前言
背包問題是一個經典的算法問題,可以用動態規划,貪心法,分支界限法等方法解決
問題描述:有n個物品,編號1,2,3,、、n,其中第 i 個物品重量為Wi 價值 Vi ,有一個容量為W的背包。
在容量允許范圍內,如何選擇物品,可以得到最大的價值。(為了簡單起見,假設物品的重量 Wi 和價值Vi 都是正數)
根據取物品的方式,背包問題又可以被分為三類:
0/1背包問題(0-1 knapsack problem)
這也是最常見的背包問題,對於每個物品,要么取走要么不取走,每個物品只能取一次。
可以用數學表達式表示為:
- maximize
- subject to
有界限的背包問題(bounded knapsack problem)
對應於上文,每個物品最多可以取Gi次.也可以不取。用數學表達式描述為:
- maximize
- subject to
注意看xi取值范圍。其中的“界限”說的是取的次數上限
無界限背包( unbounded knapsack problem (UKP))
相應的,無界限背包說的就是每個物品取的次數無限了,也就是上文中的xi 可以為任意的正整數。
今天主要說的是0、1背包問題,解法是動態規划。當然,對於另外兩種問題也會有所介紹。
問題分析:
用動態規划解問題首先要有效的找出子問題,可以通過這個子問題得推得原問題的解,通常子問題的實質是和原問題相同的,只是規模上的縮小,
也就是說子問題和原問題可以有相同的表示形式,問題可通過不斷的縮小規模(一般都會有一個界限)能找到子問題的解。
這個問題要求解的是能用背包帶走的物品的最大價值。定義 m[i,w] 為:
用第1,、2、3、、i 個物品裝入質量<=W的背包的最大價值。
m[i,w]的取值情況分析:
1) ,背包的質量為w,里面沒有物品,所以它的價值為0;
2) ,背包質量為0,所以里面沒法裝任何東西, 不論前面的 i 是多少,總價值為0;
對於任意的第 i 個物品,有兩種情況,放進背包或者不放。
不要第 i 個物品 如果 則:
3) 因為第i 個物品的重量大於背包的容量,所以不可放入。
如果. 那么
4)
對於第 i 個物品,有兩種可選擇方案:如果放入背包中,那么 m[i,w]=m[i-1,w-wi]+vi 也就是 i 的前一個的最大值加上自己的價值。
如果不要第 i 個物品,那么:m[i,w]=m[i-1,w]。也就是 i 的前一個的最大值。
因為背包問題最后要取得最大的價值,所以就選這兩種情況中價值最大的。
在這個問題中,定義子問題: m[i,w] 對於每個子問題,都可通過上面的分析求出。通過3),4)可以發現,每一次求取子問題,問題的規模就被縮小。
要么在w 上減小,要么在 i 上減小。最后問題的規模會被縮小為 m[i,0]和m[0,w].而這兩個的值都為0,只要逆向思維反推回去,就能逐步得到問題的解。
算法描述
附c++代碼:
c-free 5編譯通過

/*0、1背包問題
一條魚@博客園
http://www.cnblogs.com/yanlingyin/
2011-12-25
*/
#include <iostream>
#include<fstream>
#include<algorithm>
#include<vector>
//存儲元素的結構
typedef struct item
{
int weight;
int values;
}item;
using namespace std;
int main()
{
int weight=10;
int itemnum=4;
//int k[10][4];
vector<vector<int> > k(11,vector<int>(5));
item items[4]={{6,30},{3,14},{4,16},{2,9}};
for(int w=0;w<=weight;w++)
{
for(int j=0;j<=itemnum;j++)
{
k[w][j]=0;
}
}
//輸出數組
for(int i=0;i<=weight;i++)
{
for(int j=0;j<=itemnum;j++)
{
cout<<k[i][j];
}
cout<<"\n";
}
for(int w=1;w<=weight;w++)
{
cout<<"測試\n";
for(int j=1;j<=itemnum;j++)
{
if(items[j-1].weight>w) //物品質量大於背包容量,舍去
{
k[w][j]=k[w][j-1];
}
else //對於兩種情況選出較大值
{
k[w][j]=max(k[w][j-1],(k[w-items[j-1].weight][j-1]+items[j-1].values));
}
cout<<"k["<<w<<"]["<<j<<"]="<<k[w][j]<<"\n";
}
}
cout<<"輸出哦了"<<k[weight][itemnum]<<"\n";
for(int i=0;i<=weight;i++)
{
for(int j=0;j<=itemnum;j++)
{
cout<<k[i][j]<<" ";
}
cout<<"\n";
}
}
動態規划簡單介紹:
動態規划通常用於最優化問題,此類問題可能有很多可行解,每一個解有一個值,而我們希望找出一個具有最優值的解,
動態規划算法設計可分為如下步驟:
1)描述最優解的結構
2)遞歸定義最優解的值
3)按底向上的方式計算最優解的值
4)由計算出的結果構造一個最優解
動態規划的第一步是描述最優解的結構,如果問題的一個最優解中包含了子問題的最優解,該問題具有最優解結構。當一個子問題
有最優解結構時,提示我們動態規划適用。如本例中的m[i,w]就是一個子問題,里面包含了另一個子問題的最優解:
m[i-1,w],m[i-1,w-1]。解題的過程中把結果記錄在數組中,后面的解更具前面的解得出,最后找到問題的解。
參考資料:
http://en.wikipedia.org/wiki/Knapsack_problem
http://www.es.ele.tue.nl/education/5MC10/Solutions/knapsack.pdf
《算法導論》
歡迎轉載,請注明出處:http://www.cnblogs.com/yanlingyin/
一條魚@博客園
2011-12-25