動態規划解背包問題/C++/Knapsack problem


前言

背包問題是一個經典的算法問題,可以用動態規划,貪心法,分支界限法等方法解決

問題描述:有n個物品,編號1,2,3,、、n,其中第 i 個物品重量為Wi 價值 Vi ,有一個容量為W的背包。

在容量允許范圍內,如何選擇物品,可以得到最大的價值。(為了簡單起見,假設物品的重量 Wi 和價值Vi 都是正數)

 

根據取物品的方式,背包問題又可以被分為三類:

0/1背包問題(0-1 knapsack problem)

這也是最常見的背包問題,對於每個物品,要么取走要么不取走,每個物品只能取一次。

可以用數學表達式表示為:

  • maximize   \qquad \sum_{i=1}^n v_ix_i
  • subject to  \qquad \sum_{i=1}^n w_ix_i \leqslant W, \quad \quad x_i \in \{0,1\}
其中xi只能為1 或者0  所以稱為背包問題

有界限的背包問題(bounded knapsack problem)

對應於上文,每個物品最多可以取Gi次.也可以不取。用數學表達式描述為:

  • maximize \qquad \sum_{i=1}^n v_ix_i
  • subject to \qquad \sum_{i=1}^n w_ix_i \leqslant W, \quad \quad x_i \in \{0,1,\ldots,c_i\}
 

注意看xi取值范圍。其中的“界限”說的是取的次數上限

無界限背包( unbounded knapsack problem (UKP))

相應的,無界限背包說的就是每個物品取的次數無限了,也就是上文中的xi 可以為任意的正整數。

 

今天主要說的是0、1背包問題,解法是動態規划。當然,對於另外兩種問題也會有所介紹。

問題分析:

用動態規划解問題首先要有效的找出子問題,可以通過這個子問題得推得原問題的解,通常子問題的實質是和原問題相同的,只是規模上的縮小,

也就是說子問題和原問題可以有相同的表示形式,問題可通過不斷的縮小規模(一般都會有一個界限)能找到子問題的解。

這個問題要求解的是能用背包帶走的物品的最大價值。定義 m[i,w] 為:

用第1,、2、3、、i 個物品裝入質量<=W的背包的最大價值。

m[i,w]的取值情況分析:

1)m[0,\,w]=0  ,背包的質量為w,里面沒有物品,所以它的價值為0;

2)m[i,\,0]=0   ,背包質量為0,所以里面沒法裝任何東西, 不論前面的 i 是多少,總價值為0;

對於任意的第 i  個物品,有兩種情況,放進背包或者不放。

不要第 i  個物品 如果w_i > w\,\! 則:

3)m[i,\,w]=m[i-1,\,w]    因為第i 個物品的重量大於背包的容量,所以不可放入。

如果w_i \leqslant w. 那么

4)m[i,\,w]=\max(m[i-1,\,w],\,m[i-1,w-w_i]+v_i)  

對於第 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編譯通過

View Code
/*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

 

 

 

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM