日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

0/1背包问题-----动态规划求解

發(fā)布時間:2024/4/19 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 0/1背包问题-----动态规划求解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

問題描述

有n個物品和一個容量為c的背包,從n個物品中選取裝包的物品。物品i的重量為w[i],價值為p[i]。一個可行的背包裝載是指,裝包的物品總重量不超過背包的重量。一個最佳背包裝載是指,物品總價值最高的可行的背包裝載。
我們要求出x[i]的值。x[i] == 1表示物品i裝入背包,x[i] == 0表示物品i沒有裝入背包。
問題的公式描述是:

//總價值最高的可能的背包裝載,x[i]只能等于0或者1 max{p[1] * x[1] + p[2] * x[2] + ... + p[i] * x[i] + ... + p[n] * x[n]}

約束條件

//裝入的所有物品的重量之和不大于背包容量 size_t totalWeight = 0; for(size_t i = 1; i <= n; ++i) {totalWeight += w[i] * x[i]; //x[i] = 0 || x[i] = 1 } if(totalWeight <= c)//約束條件成立 else//約束條件不成立

問題分析

考察這樣一個0/1背包問題: 總共4個物品,重量分別是w[1:4] = {8, 6, 2, 3},價值分別是p[1:4] = {8, 6, 4, 5},規(guī)定背包容量為12(即可以容納的最大重量為12),求出獲得最大價值的解情況。
如前所述,在該問題中,我們需要選擇x[1],…,x[n]的值。假定我們按物品i = 1,2,…,n的順序選擇x[i]的值。如果選擇x[1]=0,那么背包問題就轉(zhuǎn)變?yōu)槲锲?,3,…,n,背包容量仍為c的問題。如果選擇x[1]=1,那么背包問題就轉(zhuǎn)變?yōu)槲锲?,3,…,n,背包容量為c-w[1]的問題。
所以在確定了物品i是否裝入之后,背包問題就轉(zhuǎn)變?yōu)閕+1,i+2,…,n這些物品裝入背包容量為c’或c’-w[i]的子問題(c’表示確定物品i是否裝入之前背包所剩的容量)
又因?yàn)槲锲穒裝入與不裝入是等可能的,在確定物品i的時候我們無法確定是裝入它可以獲得最優(yōu)解還是不裝入它可以獲得最優(yōu)解,所以需要分別計(jì)算這兩種情況,然后取最優(yōu)的一個。

假設(shè)f(i, y)表示容量為y,物品為i,i+1,…,n的背包問題的最優(yōu)解的值,也就是說f(i, y)返回的值是物品從i到n,容量為y這個子背包可以獲得的最大價值(最優(yōu)解的值)。所以可以寫出f的函數(shù)表達(dá)式:

//對于f(n, y)而言,即考慮最后一個物品是否裝入時, //如果剩余容量大于物品n的重量(即足夠裝入物品n),則返回的就是物品n的價值, //否則,不裝入物品n,返回的價值為0 if(y >= w[n])f(n, y) = p[n]; else //y >= 0 && y < w[n]f(n, y) = 0; //對于f(i, y)而言,i >= 1 && i < n,即考察除最后一個物品之外的其他物品是否裝入時, //如果當(dāng)前可用容量小于物品i的重量(即裝不下物品i),則表示x[i] = 0, //返回當(dāng)前容量仍為y,物品從i+1到n的背包子問題的最優(yōu)解 //如果當(dāng)前可用容量大于等于物品i的重量(即可以裝下物品i),則物品i裝入和不裝入是等可能的, //需要考慮兩種情況取最優(yōu)的那一個 if(y >= w[i])f(i, y) = max(f(i+1, y), f(i+1, y-w[i]) + p[i]); else //y >= 0 && y < w[i]f(i, y) = f(i+1, y);

根據(jù)上述分析得到的公式結(jié)論,即最優(yōu)序列由最優(yōu)子序列構(gòu)成的結(jié)論,可以利用上述f的遞歸式實(shí)現(xiàn)遞歸求解

遞歸求解

很多類似的題都是利用遞歸的方式求解的,因?yàn)檫f歸的思路比較簡單,容易理解。唯一的缺點(diǎn)就是遞歸會造成大量的棧空間消耗(因?yàn)槊繉舆f歸都會傳參,又要保留上層遞歸程序運(yùn)行的地址,同時又存在返回值的傳遞)。不過拋開這些,了解遞歸是如何求解問題還是很有必要的,后面會有利用迭代的方法求解。

為了減少遞歸調(diào)用不必要的傳參消耗,可以把很多變量作為全局變量,在背包問題中,可以把背包容量Capacity,物品數(shù)量n,每個物品的重量(顯然用數(shù)組存儲比較合理)Weight[],以及每個物品的價值(同樣利用數(shù)組存儲)Profit[]作為全局變量。
這樣遞歸函數(shù)只需要兩個參數(shù)就可以了(像上面的f(i, y)一樣),i表示物品i,y表示當(dāng)前剩余容量。然后一步步將上面的公式轉(zhuǎn)換成代碼。

//全局變量 int n; int *Weight; int *Profit;//函數(shù)返回的是物品i,i+1,...,n,背包容量為y時的最優(yōu)解 int Backpack(size_t i, size_t y) {//考察f(n, y)的情況//可以裝下物品n時返回物品n的價值//裝不下時返回0if(i == n){return y >= Weight[i] ? Profit[i] : 0;}//考察f(i, y)的情況//分兩種情況,取最大值//第一種:物品i沒有裝入背包,可用容量y不變,開始裝入物品i+1,...,n//第二種:物品i裝入背包,可用容量變?yōu)閥-Weight[i],開始裝入物品i+1,...,n.同時還應(yīng)加上物品i的價值else{return max(Backpack(i + 1, y),Backpack(i + 1, y - Weight[i]) + Profit[i]);} }

之后考慮一個問題,0/1背包問題求得的結(jié)果是每個物品的裝入情況,換句話說就是求x[i](i >= 1 && i<=n),當(dāng)x[i] == 0時表示物品i沒有被裝入背包,當(dāng)x[i] == 1時表示物品i被裝入背包。

上面的遞歸程序可以讓我們求得f(i, y),即當(dāng)前可用容量為y,解決物品i,i+1,…,n的背包子問題。返回的值是這個子問題可以獲得的最大價值量,也就是最優(yōu)解。

然后考慮一下f(i, y)和f(i+1, y)的含義(這里兩個y表示的數(shù)值相同,都表示考慮物品i的裝入問題時,當(dāng)前背包的可用容量)
f(i, y)表示從物品i到物品n可以獲得的總價值(不知道i是否被裝入)
f(i+1,y)表示的是物品i沒有裝入背包的情況下,從物品i到物品n可以獲得的總價值。
所以只有當(dāng)物品i沒有被裝入背包時,f(i, y)才與f(i+1, y)相等

綜上,判斷物品i是否被裝入背包,只需要判斷f(i, y)是否等于f(i+1, y)即可。所以在遞歸過程中還需要記錄每一個f(i, y)的值,用于在最后輸出裝入情況。

f(i, y)的值可以記錄在二維數(shù)組中,把它作為全局變量使用。更新后的代碼如下:

//全局變量 int n; int *Weight; int *Profit; int **Solution; //Solution[i][y]表示當(dāng)前背包容量為y,物品i,i+1,...,n的背包裝入情況可以獲得的最優(yōu)解的值 //初始時Solution[i][y] = 0//函數(shù)返回的是物品i,i+1,...,n,背包容量為y時的最優(yōu)解 int Backpack(size_t i, size_t y) {//當(dāng)Solution[i][y]不為0時,說明在之前已經(jīng)計(jì)算過這種情況下的背包子問題,可以直接返回//這樣做可以減少重復(fù)的遞歸計(jì)算if(Solution[i][y] != 0)return Solution[i][y];int solution;//考察f(n, y)的情況//可以裝下物品n時返回物品n的價值//裝不下時返回0if(i == n){solution = y >= Weight[i] ? Profit[i] : 0;}//考察f(i, y)的情況//分兩種情況,取最大值//第一種:物品i沒有裝入背包,可用容量y不變,開始裝入物品i+1,...,n//第二種:物品i裝入背包,可用容量變?yōu)閥-Weight[i],開始裝入物品i+1,...,n.同時還應(yīng)加上物品i的價值else{solution = max(Backpack(i + 1, y),Backpack(i + 1, y - Weight[i]) + Profit[i]);}//存儲求得的最優(yōu)解Solution[i][y] = solution;return Solution[i][y]; }

到目前為止遞歸程序就大致完成了。整個遞歸下來每種情況的最優(yōu)解都記錄在Solution二維數(shù)組中,而獲得的最大價值則是遞歸程序Backpack(1, Capacity)的返回值,可以根據(jù)Solution二維數(shù)組輸出背包裝入的結(jié)果。

void printSolution() {//最大價值量是Backpack(1, Capacity)的返回值size_t pCurrentCapacity = Capacity;for(size_t i = 1; i < n; ++i){//如果f(i, y) == f(i+1, y)說明物品i沒有被裝入背包if(Solution[i][pCurrentCapacity] == Solution[i+1][pCurrentCapacity]){std::cout << "Backpack" << i << ": " << 0 << std::endl;}//反之,則被裝入背包,相應(yīng)的剩余背包容量要減去物品i的重量else{ std::cout << "Backpack" << i << ": " << 1 << std::endl;pCurrentCapacity -= Weight[i];}}//判斷最后一個物品時不能像判斷前n-1個物品一樣,因?yàn)樽詈笠粋€物品沒有第n+1個物品//所以只需要判斷剩余容量能否裝入物品n即可if(pCurrentCapacity >= Weight[n])std::cout << "Backpack" << n << ": " << 1 << std::endl;elsestd::cout << "Backpack" << n << ": " << 0 << std::endl; }

迭代求解

相比遞歸,迭代求解的好處是減少了遞歸反復(fù)調(diào)用的棧開銷(這也是為什么將多數(shù)變量作為全局變量而不作為參數(shù)傳遞的原因),但是迭代在理解上并沒有遞歸那樣易于理解。

遞歸是從想要求的解開始(這里是Backpack(1, Capacity)),一步步深入到最底層,比如最開始只是調(diào)用Backpack(1, Capacity),一層層遞歸到Backpack(n, y),然后逐層向上返回。
而迭代則恰好與遞歸相反,也可以理解為迭代是將遞歸向上返回的過程呈現(xiàn)出來。也就是從物品n開始,最后求得物品1,得到想要的結(jié)果。

考慮兩個事情。
1.遞歸程序中使用的二維數(shù)組Solution,Solution[i][y]表示當(dāng)前可用容量為y,物品i,i+1,…,n的背包裝入情況的最優(yōu)解的值。
2.f(i, y)的公式中分為兩種情況,一種是y小于物品i的重量,此時f(i, y) = f(i+1, y)。另一種是y大于等于物品i的重量,此時f(i, y)等于裝入物品i和不裝入物品i這兩種情況的最大值

結(jié)合二者來看
1.當(dāng)Solution[i][y]中的y小于物品i的重量時,Solution[i][y]應(yīng)該等于Solution[i+1][y]的值。
2.當(dāng)Solution[i][y]中的y大于等于物品i的重量時,Solution[i][y]應(yīng)該等于Solution[i+1][y]和Solution[i+1][y-Weight[i]] + Profit[i]中的最大值。

又因?yàn)樵诔绦驁?zhí)行過程中,y的值可能是從0到Capacity中的任何一個,所以需要把每種情況都計(jì)算在內(nèi)。分割線便是物品i的重量:
1.y從0到Weight[i]-1是一種情況,此時當(dāng)前可用容量不足以裝入物品i,Solution[i][y] = Solution[i+1][y]。
2.y從Weight[i]到Capacity是另一種情況,此時當(dāng)前可用容量可以裝入物品i,Solution[i][y] = max(Solution[i+1][y], Solution[i+1][y-Weight[i]] + Profit[i])。

程序中需要把二維數(shù)組Solution中的每一個元素都計(jì)算出來。

void Backpack(int Weight[], int Profit, int n, int Capacity) {int **Solution = new int*[n];for(size_t i = 1; i <= n; ++i){Solution[i] = new int[Capacity+1];for(size_t j = 0; j <= Capacity; ++j)Solution[i][j] = 0;}//單獨(dú)考慮最后一個物品,//y在0到Weight[n]-1時,表示當(dāng)前可用容量裝不下物品n,f(n, y) = 0//y在Weight[n]到Capacity時,表示當(dāng)前可用容量可以裝下物品n,f(n, y) = Profit[n]int yMin = min(Weight[n] - 1, Capacity);for(size_t y = 0; y <= yMin; ++y)Solution[n][y] = 0;for(size_t y = Weight[n]; y <= Capacity; ++y)Solution[n][y] = Profit[n];//考慮從物品n-1到2//y在0到Weight[i]-1時,表示當(dāng)前可用容量裝不下物品i,f(i, y) = f(i+1, y);//y在Weight[i]到Capacity時,表示當(dāng)前可用容量可以裝下物品i,f(i, y) = max(f(i+1, y), f(i+1, y - Weight[i]) + Profit[i]);for(size_t i = n - 1; i > 1; --i){//取總?cè)萘亢臀锲穒重量的較小值//因?yàn)楫?dāng)物品i的重量是大于總?cè)萘康?#xff0c;則默認(rèn)不裝入物品iyMin = min(Weight[i] - 1, Capacity); for(size_t y = 0; y <= yMin; ++y)Solution[i][y] = Solution[i+1][y];for(size_t y = Weight[i]; y <= Capacity; ++y)Solution[i][y] = max(Solution[i+1][y], Solution[i+1][y-Weight[i]] + Profit[i]);}//單獨(dú)考慮物品1,它不需要求出y從0到Capacity的所有情況,只需求得Solution[1][Capacity]即可Solution[1][Capacity] = Solution[2][Capacity];if(Capacity >= Weight[1])Solution[1][Capacity] = max(Solution[1][Capacity],Solution[2][Capacity-Weight[1]] + Profit[1]); }

在遞歸程序中,考慮完第i個物品后,開始考慮第i+1個物品時,剩余容量是間斷的幾個值,即要不是y,就是y-Weight[i],其他的值不需要考慮。然而在迭代程序中,程序是從第n個物品開始考慮的,然后考慮n-1,n-2,…,1,又因?yàn)楫?dāng)前剩余容量的定義是考慮完第1,2,…,i-1個物品后,考慮第i個物品時可用的容量。這就導(dǎo)致了無法預(yù)先知道在考慮完第i個物品后,考慮第i-1個物品時的剩余容量是多少,這就需要把所有可能的剩余容量都考慮一遍。
輸出每一個背包是否裝入的情況,和遞歸程序的輸出函數(shù)是一樣的,因?yàn)镾olution中的一些關(guān)鍵部分的值都已經(jīng)求好了。

總結(jié)

以上是生活随笔為你收集整理的0/1背包问题-----动态规划求解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。