<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      01背包問題,經典收藏,

      01背包問題描述

      已知:有一個容量為V的背包和N件物品,第i件物品的重量是weight[i],收益是cost[i]。

      限制:每種物品只有一件,可以選擇放或者不放

      問題:在不超過背包容量的情況下,最多能獲得多少價值或收益

      相似問題:在恰好裝滿背包的情況下,最多能獲得多少價值或收益

      這里,我們先討論在不超過背包容量的情況下,最多能獲得多少價值或收益。

      基本思路

      01背包的特點:每種物品只有一件,可以選擇放或者不放

      子問題定義狀態

      1. f[i][v] : 前i件物品放到一個容量為v的背包中可以獲得最大價值  
      f[i][v] : 前i件物品放到一個容量為v的背包中可以獲得最大價值

      狀態轉移方程

      1. f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])  
      f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])

      分析

      考慮我們的子問題,將前i件物品放到容量為v的背包中,若我們只考慮第i件物品時,它有兩種選擇,放或者不放。

      1) 如果第i件物品不放入背包中,那么問題就轉換為:將前i - 1件物品放到容量為v的背包中,帶來的收益f[i - 1][v]

      2) 如果第i件物品能放入背包中,那么問題就轉換為:將前i - 1件物品放到容量為v - weight[i]的背包中,帶來的收益f[i - 1][v - weight[i]] + cost[i]

      代碼

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int N = 3;//物品個數   
      5. const int V = 5;//背包最大容量   
      6. int weight[N + 1] = {0,3,2,2};//物品重量   
      7. int value[N + 1] = {0,5,10,20};//物品價值   
      8.   
      9. int f[N + 1][V + 1] = {{0}};  
      10.   
      11. int Max(int x,int y)  
      12. {  
      13.     return x > y ? x : y;  
      14. }  
      15.   
      16. /* 
      17. 目標:在不超過背包容量的情況下,最多能獲得多少價值 
      18.  
      19. 子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值 
      20.  
      21. 狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
      22.  
      23. 初始化:f數組全設置為0 
      24. */  
      25. int Knapsack()  
      26. {  
      27.     //初始化   
      28.     memset(f,0,sizeof(f));  
      29.     //遞推   
      30.     for (int i = 1;i <= N;i++) //枚舉物品   
      31.     {  
      32.         for (int j = 0;j <= V;j++) //枚舉背包容量   
      33.         {  
      34.             f[i][j] = f[i - 1][j];  
      35.             if (j >= weight[i])  
      36.             {  
      37.                 f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);  
      38.             }  
      39.         }  
      40.     }  
      41.     return f[N][V];  
      42. }  
      43.   
      44. int main()  
      45. {  
      46.     cout<<Knapsack()<<endl;  
      47.     system("pause");  
      48.     return 1;  
      49. }  
      #include <iostream>
      using namespace std;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[N + 1][V + 1] = {{0}};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在不超過背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	memset(f,0,sizeof(f));
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = 0;j <= V;j++) //枚舉背包容量
      		{
      			f[i][j] = f[i - 1][j];
      			if (j >= weight[i])
      			{
      				f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
      			}
      		}
      	}
      	return f[N][V];
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;
      	system("pause");
      	return 1;
      }

      效率分析:

      此算法的時間復雜度為O(N*V),空間復雜度也為O(N*V)。其中,N 表示物品個數,V 表示背包容量這里,時間復雜度不可以在優化了,但是空間復雜度可以繼續優化到O(V)

      優化空間復雜度

      上述的方法,我們使用二維數組 f[i][v] 保存中間狀態,這里我們可以使用一維數組f[v]保存中間狀態就能得到結果

      分析

      我們現在使用f[v]保存中間狀態,我們想要達到的效果是,第i次循環后,f[v]中存儲的是前i個物體放到容量v時的最大價值

      在回顧下之前講過的狀態轉移方程:

      1. f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])  
      f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])

      我們可以看到,要想得到 f[i][v],我們需要知道 f[i - 1][v] 和 f[i - 1][v - weight[i]],由于我們使用二維數組保存中間狀態,所以可以直接取出這兩個狀態。

      當我們使用一維數組存儲狀態時,f[v]表示,在執行i次循環后(此時已經處理i個物品),前i個物體放到容量v時的最大價值,即之前的f[i][v]。與二維相比較,它把第一維隱去了,但是二者表達的含義還是相同的,只不過針對不同的i,f[v]一直在重復使用,所以,也會出現第i次循環可能會覆蓋第i - 1次循環的結果。

      為了求f[v],我們需要知道,前i - 1個物品放到容量v的背包中帶來的收益,即之前的f[i - 1][v]  和 前i - 1件物品放到容量為v - weight[i]的背包中帶來的收益,即之前的f[i - 1][v - weight[i]] + cost[i]。

      難點:由于我們只使用一維數組存儲,則在求這兩個子問題時就沒有直接取出那么方便了,因為,第i次循環可能會覆蓋第i - 1次循環的結果。

      現在我們來求這兩個值

      1)前i - 1個物品放到容量v的背包中帶來的收益,即之前的f[i - 1][v] :

      由于,在執行在i次循環時,f[v]存儲的是前i個物體放到容量v時的最大價值,在求前i個物體放到容量v時的最大價值(即之前的f[i][v])時,我們是正在執行第 i 次循環,f[ v ]的值還是在第 i - 1  次循環時存下的值,在此時取出的 f[ v ]就是前i - 1個物體放到容量v時的最大價值,即f[i - 1][v]。

      2)前i - 1件物品放到容量為v - weight[i]的背包中帶來的收益,即之前的f[i - 1][v - weight[i]] + cost[i]

      由于,在執行第i次循環前,f[0 ~ V]中保存的是第i - 1次循環的結果,即是前i - 1個物體分別放到容量0 ~ V時的最大價值,即f[i - 1][0 ~ V]。

      則,在執行第i次循環前,f 數組中v - weight[i]的位置存儲就是我們要找的 前i - 1件物品放到容量為v - weight[i]的背包中帶來的收益 (即之前的f[i - 1][v - weight[i]]),這里假設物品是從數組下標1開始存儲的。

      偽代碼

      1. for i=1..N //枚舉物品   
      2.     for v=V..0 //枚舉容量,從大到小   
      3.         f[v]=max{f[v],f[v-weight[i]] + cost[i]};  
      for i=1..N //枚舉物品
          for v=V..0 //枚舉容量,從大到小
              f[v]=max{f[v],f[v-weight[i]] + cost[i]};

      由上面偽代碼可知,在執行第 i 次循環時,需要把背包容量由V..0都要遍歷一遍,檢測第 i 件物品是否能放。

      逆序枚舉容量的原因:

      注意一點,我們是由第 i - 1 次循環的兩個狀態推出 第 i 個狀態的,而且 v  > v - weight[i],則對于第i次循環,背包容量只有當V..0循環時,才會先處理背包容量為v的狀況,后處理背包容量為 v-weight[i] 的情況。

      具體來說,由于,在執行v時,還沒執行到v - weight[i]的,因此,f[v - weight[i]]保存的還是第i - 1次循環的結果。即在執行第i次循環 且 背包容量為v時,此時的f[v]存儲的是 f[i - 1][v] ,此時f[v-weight[i]]存儲的是f[i - 1][v-weight[i]]。

      相反,如果在執行第 i 次循環時,背包容量按照0..V的順序遍歷一遍,來檢測第 i 件物品是否能放。此時在執行第i次循環 且 背包容量為v時,此時的f[v]存儲的是 f[i - 1][v] ,但是,此時f[v-weight[i]]存儲的是f[i][v-weight[i]]。

      因為,v  > v - weight[i],第i次循環中,執行背包容量為v時,容量為v - weight[i]的背包已經計算過,即f[v - weight[i]]中存儲的是f[i][v - weight[i]]。即,對于01背包,按照增序枚舉背包容量是不對的。

      代碼

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int N = 3;//物品個數   
      5. const int V = 5;//背包最大容量   
      6. int weight[N + 1] = {0,3,2,2};//物品重量   
      7. int value[N + 1] = {0,5,10,20};//物品價值   
      8.   
      9. int f[V + 1] = {0};  
      10.   
      11. int Max(int x,int y)  
      12. {  
      13.     return x > y ? x : y;  
      14. }  
      15.   
      16. /* 
      17. 目標:在不超過背包容量的情況下,最多能獲得多少價值 
      18.  
      19. 子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值 
      20.  
      21. 狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]} 
      22.  
      23. 初始化:f數組全設置為0 
      24. */  
      25. int Knapsack()  
      26. {  
      27.     //初始化   
      28.     memset(f,0,sizeof(f));  
      29.     //遞推   
      30.     for (int i = 1;i <= N;i++) //枚舉物品   
      31.     {  
      32.         for (int j = V;j >= weight[i];j--) //枚舉背包容量,防越界,j下限為 weight[i]   
      33.         {  
      34.             f[j] = Max(f[j],f[j - weight[i]] + value[i]);  
      35.         }  
      36.     }  
      37.     return f[V];  
      38. }  
      39.   
      40. int main()  
      41. {  
      42.     cout<<Knapsack()<<endl;  
      43.     system("pause");  
      44.     return 1;  
      45. }  
      #include <iostream>
      using namespace std;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[V + 1] = {0};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在不超過背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	memset(f,0,sizeof(f));
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = V;j >= weight[i];j--) //枚舉背包容量,防越界,j下限為 weight[i]
      		{
      			f[j] = Max(f[j],f[j - weight[i]] + value[i]);
      		}
      	}
      	return f[V];
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;
      	system("pause");
      	return 1;
      }

      但是,增序枚舉背包容量會達到什么效果:它會重復的裝入某個物品,而且盡可能多的,使價值最大,當然不會不超過背包容量

      而逆序枚舉背包容量:背包中的物品至多裝一次,使價值最大,當然不會不超過背包容量

      我們首先舉例說明:

      逆序枚舉物品

      當i = 2,我們要求 f [5]:表示檢測物品2放入容量為5的背包的最大收益

      上圖表示,當i = 2,求f[5]時f數組的狀況,

      橙色為數組現在存儲的值,這些值是i = 1時(上一次循環)存入數組 f 的。相當于f[i - 1][v]

      而黃色使我們要求的值,在求f[5]之前,f[5]= 5,即f[i - 1][5] = 5

      現在要求 i = 2 時的f[5] = f[5 - 2] + 10 = 5 + 10 = 15  >  f[i - 1][5] = 5

      故,f[5] = 15;

      注意一點,在求f[v]時,它引用的 f[v - weight[i]] 和 f[v]都是上一次循環的結果

      順序枚舉物品

      當i = 2,我們要求 f [5]:表示檢測物品2放入容量為5的背包的最大收益

      上圖表示,當i = 2,求f[5]時f數組的狀況,

      橙色為數組現在存儲的值,這些值是i = 2時(本次循環)存入數組 f 的。相當于f[i][v]

      這是由于,我們是增序遍歷數組f的,在求f[v]時,v之前的值(0 ~ v - 1)都已經在第i次循環中求出。

      而黃色使我們要求的值,在求f[5]之前,f[5]= 5,即f[i - 1][5] = 5

      現在要求 i = 2 時的f[5] = f[5 - 2] + 10 =10+ 10 = 20 >  f[i - 1][5] = 5

      故,f[5] = 20;

      其中引用的f[3]是相當于f[i][3] 而不是正常的f[i - 1][3]

      注意一點,在求f[v]時,它引用的 f[v - weight[i]]是本次循環的結果 而f[v]是上一次循環的結果

      換個角度說,

      在檢測 背包容量為5時,看物品2是否加入

      由狀態轉移方程可知,我們f[5]需要引用自己本身和f[3]

      由于背包容量為3時,可以裝入物品2,且收益比之前的大,所以放入背包了。

      在檢測f[5]時,肯定要加上物品2的收益,而f[5]在引用f[3]時,f[3]時已經加過一次物品2,

      因此,在枚舉背包容量時,物品2會加入多次。

      進一步說:

      我們觀察一維狀態轉移方程:

      f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])

      首先我們明確三個問題

      1) v - weight[i] < v

      2) 狀態f[i][v] 是由 f[i - 1][v] 和 f[i - 1][v - weight[i]] 兩個狀態決定

      3) 對于物品i,我們在枚舉背包容量時,只要背包容量能裝下物品i 且 收益比原來的大,就會成功放入物品i。

      具體來說,枚舉背包容量時,是以遞增的順序的話,由于 v - weight[i] < v,則會先計算 v - weight[i]。在背包容量為v - weight[i]時,一旦裝入了物品i,由于求f[v]需要使用f[i - 1][v - weight[i]],而若求f[v]時也可以裝入物品i的話,那么在背包容量為v時,容量為v的背包就裝入可兩次物品。又若v - weight[i]是由之前的狀態推出,它們也成功裝入物品i的話,那么容量為v的背包就裝入了多次物品i了。

      注意,此時,在計算f[v]時,已經把物品i能裝入的全裝入容量為v的背包了,此時裝入物品i的次數為最大啊

      其實,順序枚舉容量是完全背包問題最簡捷的解決方案。

       

      初始化的細節問題

      求最優解的背包問題時,有兩種問法:

      1)在不超過背包容量的情況下,最多能獲得多少價值

      2)在恰好裝滿背包的情況下,最多能獲得多少價值

      主要的區別為是否要求恰好裝滿背包。但這兩種問法的實現方法是在初始化的時候有所不同。

      1)恰好裝滿背包的情況:使用二維數組f[i][v]存儲中間狀態,其中第一維表示物品,第二維表示背包容量

      初始化時,除了f[i][0] = 0(第一列)外,其他全為負無窮。

      原因:初始化 f 數組就是表示:在沒有任何物品可以放入背包時的合法狀態。對于恰好裝滿背包,只有背包容量為 0(第一列),可以什么物品都不裝就能裝滿,這種情況是合法情況,此時價值為0。其他f[0][v](第一列)是都不能裝滿的,此時有容量沒物品。而其他位置(除去第一行和第一列的位置),我們為了在計算中比較最大值,也要初始化為負無窮。我們從程序的角度上看,我們只允許裝入背包物品的序列的起始位置是從第一列開始,這些起始位置都是合法位置,且能恰好裝滿的情況收益均為正值,到f[N][V]終止。

      注意,我們雖然是求恰好裝滿,還是需要枚舉所有可以裝入背包的物品,只要能裝入,還需裝入,收益有增加。只不過,由于恰好裝滿的物品的序列肯定是從第一列某行開始的,且之后的收益肯定是正值。對于非恰好裝滿的物品序列,其實位置肯定是從第一行某位置開始的,由于此時被初始化為負無窮,在和那些恰好裝滿物品序列帶來的價值時,肯定是小的。所以,我們最后能獲得最大值。

      代碼:

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int MinNum = 0x80000000;  
      5.   
      6. const int N = 3;//物品個數   
      7. const int V = 5;//背包最大容量   
      8. int weight[N + 1] = {0,3,2,2};//物品重量   
      9. int value[N + 1] = {0,5,10,20};//物品價值   
      10.   
      11. int f[N + 1][V + 1] = {{0}};  
      12.   
      13. int Max(int x,int y)  
      14. {  
      15.     return x > y ? x : y;  
      16. }  
      17.   
      18. /* 
      19. 目標:在恰好裝滿背包的情況下,最多能獲得多少價值 
      20.  
      21. 子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值 
      22.  
      23. 狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
      24.  
      25. 初始化:f數組全設置為0 
      26. */  
      27. int Knapsack()  
      28. {  
      29.     //初始化   
      30.     for (int i = 0;i <= N;i++) //枚舉物品   
      31.     {  
      32.         for (int j = 0;j <= V;j++) //枚舉背包容量   
      33.         {  
      34.             f[i][j] = MinNum;  
      35.         }  
      36.     }  
      37.     for (int i = 0;i <= N;i++)  
      38.     {  
      39.         f[i][0] = 0;//背包容量為0時為合法狀態   
      40.     }  
      41.     //遞推   
      42.     for (int i = 1;i <= N;i++) //枚舉物品   
      43.     {  
      44.         for (int j = 1;j <= V;j++) //枚舉背包容量   
      45.         {  
      46.             f[i][j] = f[i - 1][j];  
      47.             if (j >= weight[i])  
      48.             {  
      49.                 f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);  
      50.             }  
      51.         }  
      52.     }  
      53.     return f[N][V];  
      54. }  
      55.   
      56. int main()  
      57. {  
      58.     cout<<Knapsack()<<endl;//輸出25   
      59.     system("pause");  
      60.     return 1;  
      61. }  
      #include <iostream>
      using namespace std;
      
      const int MinNum = 0x80000000;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[N + 1][V + 1] = {{0}};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在恰好裝滿背包的情況下,最多能獲得多少價值
      
      子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	for (int i = 0;i <= N;i++) //枚舉物品
      	{
      		for (int j = 0;j <= V;j++) //枚舉背包容量
      		{
      			f[i][j] = MinNum;
      		}
      	}
      	for (int i = 0;i <= N;i++)
      	{
      		f[i][0] = 0;//背包容量為0時為合法狀態
      	}
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = 1;j <= V;j++) //枚舉背包容量
      		{
      			f[i][j] = f[i - 1][j];
      			if (j >= weight[i])
      			{
      				f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
      			}
      		}
      	}
      	return f[N][V];
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;//輸出25
      	system("pause");
      	return 1;
      }

      使用一維數組f[v]存儲中間狀態,維表示背包容量

      初始化時,除了f[0] = 0,其他全為負無窮。

      原因:只有容量為0 的背包可以什么物品都不裝就能裝滿,此時價值為0。其它容量的背包均沒有合法的解,屬于未定義的狀態,應該被賦值為負無窮了

      代碼

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int MinNum = 0x80000000;//int最小的數   
      5.   
      6. const int N = 3;//物品個數   
      7. const int V = 5;//背包最大容量   
      8. int weight[N + 1] = {0,3,2,2};//物品重量   
      9. int value[N + 1] = {0,5,10,20};//物品價值   
      10.   
      11. int f[V + 1] = {0};  
      12.   
      13. int Max(int x,int y)  
      14. {  
      15.     return x > y ? x : y;  
      16. }  
      17.   
      18. /* 
      19. 目標:在恰好裝滿背包容量的情況下,最多能獲得多少價值 
      20.  
      21. 子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值 
      22.  
      23. 狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]} 
      24.  
      25. 初始化:f數組全設置為0 
      26. */  
      27. int Knapsack()  
      28. {  
      29.     //初始化   
      30.     for (int i = 0;i <= V;i++)  
      31.     {  
      32.         f[i] = MinNum;  
      33.     }  
      34.     f[0] = 0;//只有背包容量為0時才是合法狀態,由合法狀態組成的結果才是合法的   
      35.   
      36.     //遞推   
      37.     for (int i = 1;i <= N;i++) //枚舉物品   
      38.     {  
      39.         for (int j = V;j >= weight[i];j--) //枚舉背包容量,防越界,j下限為 weight[i]   
      40.         {  
      41.             f[j] = Max(f[j],f[j - weight[i]] + value[i]);  
      42.         }  
      43.     }  
      44.     return f[V];  
      45. }  
      46.   
      47. int main()  
      48. {  
      49.     cout<<Knapsack()<<endl;//輸出25   
      50.     system("pause");  
      51.     return 1;  
      52. }  
      #include <iostream>
      using namespace std;
      
      const int MinNum = 0x80000000;//int最小的數
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[V + 1] = {0};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在恰好裝滿背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	for (int i = 0;i <= V;i++)
      	{
      		f[i] = MinNum;
      	}
      	f[0] = 0;//只有背包容量為0時才是合法狀態,由合法狀態組成的結果才是合法的
      
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = V;j >= weight[i];j--) //枚舉背包容量,防越界,j下限為 weight[i]
      		{
      			f[j] = Max(f[j],f[j - weight[i]] + value[i]);
      		}
      	}
      	return f[V];
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;//輸出25
      	system("pause");
      	return 1;
      }

      2)不需要把背包裝滿,只需要收益最大

      使用二維數組f[i][v]存儲中間狀態,其中第一維表示物品,第二維表示背包容量

      初始化時,除了f[i][0] = 0(第一列)外,其他全為負無窮。

      使用一維數組f[v]存儲中間狀態,維表示背包容量

      原因:如果背包并非必須被裝滿,那么任何容量的背包都有一個合法解“什么都不裝”,這個解的價值為0,所以初始時狀態的值也就全部為0了。

      代碼在最前面已貼,不在此上傳。

      一個常數優化

      一維數組描述狀態時的偽代碼:

      1. for i=1..N //枚舉物品   
      2.     for v=V..0 //枚舉容量,從大到小   
      3.         f[v]=max{f[v],f[v-weight[i]] + cost[i]};  
      for i=1..N //枚舉物品
          for v=V..0 //枚舉容量,從大到小
              f[v]=max{f[v],f[v-weight[i]] + cost[i]};

      觀察可知,對于第i個物品,枚舉背包容量下限時,可以到weight[i]為止。

      原因:

      1)對于第i物品,在求f[v]時,需要使用的狀態是 v ~ v -  weight[i] 這么多,這是由于v取最大容量V時,使用的狀態才是v - weight[i],當v不取最大狀態時,使用的狀態肯定是在v ~ v -  weight[i]之間的。可以到weight[i]為止。

      2)在到weight[i]為止時,還可以不進行if判斷,擔心v -  weight[i]是否越界

      此時,偽代碼為

      1. for i=1..N //枚舉物品   
      2.     for v=V..weight[i] //枚舉容量,從大到小   
      3.         f[v]=max{f[v],f[v-weight[i]] + cost[i]};  
      for i=1..N //枚舉物品
          for v=V..weight[i] //枚舉容量,從大到小
              f[v]=max{f[v],f[v-weight[i]] + cost[i]};

      注意,對 f 數組,如果是檢測第i個物品是否能放入,0 ~ weight[i] - 1的這些位置是不會遍歷到的,則此時他們仍表示第i - 1次的狀態,即二維的f[i - 1][v]。

      還可以繼續優化下界為

      1. for i=1..N //枚舉物品   
      2.     bound=max{V-sum{weight[i..n]},weight[i]}//確定需要枚舉容量的下界   
      3.     for v=V..bound  
      4.         f[v]=max{f[v],f[v-weight[i]] + cost[i]};  
      for i=1..N //枚舉物品
      	bound=max{V-sum{weight[i..n]},weight[i]}//確定需要枚舉容量的下界
      	for v=V..bound
      		f[v]=max{f[v],f[v-weight[i]] + cost[i]};

      原因:

      1)網上的說法,不太懂,各位大牛可以指導下下。

      對于第i次循環(指外循環),對于背包容量v = V(最大)時,對于f[v]的值,其實只要知道f[v-weight[i]]即可。以此類推,對于背包容量為 j 時,我們只需要知道到f[v-sum{weight[j..n]}]即可

      2)還有人說

      如果比v-sum{weight[j..n]}這個小,那么即使后面物品的全要也裝不滿背包。

      所以對于物品i,小于v-sum{weight[j..n]}的v值,無意義。

      總之是不懂。智商啊

      作者說,當V很大是,效果好。

      代碼

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int N = 3;//物品個數   
      5. const int V = 5;//背包最大容量   
      6. int weight[N + 1] = {0,3,2,2};//物品重量   
      7. int value[N + 1] = {0,5,10,20};//物品價值   
      8.   
      9. int f[V + 1] = {0};  
      10.   
      11. int Max(int x,int y)  
      12. {  
      13.     return x > y ? x : y;  
      14. }  
      15.   
      16. /* 
      17. 目標:在不超過背包容量的情況下,最多能獲得多少價值 
      18.  
      19. 子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值 
      20.  
      21. 狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]} 
      22.  
      23. 初始化:f數組全設置為0 
      24. */  
      25. int Knapsack()  
      26. {  
      27.     int sum = 0;//存儲還未處理物品的總容量   
      28.     int bound = 0;  
      29.     //初始化   
      30.     memset(f,0,sizeof(f));  
      31.     for (int i = 1;i <= N;i++)  
      32.     {  
      33.         sum += weight[i];  
      34.     }  
      35.       
      36.     //遞推   
      37.     for (int i = 1;i <= N;i++) //枚舉物品   
      38.     {  
      39.         //設置下界   
      40.         if (i != 1)  
      41.         {  
      42.             sum -= weight[i - 1];  
      43.         }  
      44.         bound = Max(V - sum,weight[i]);  
      45.   
      46.         for (int j = V;j >= bound;j--) //枚舉背包容量   
      47.         {  
      48.             if (f[j] < f[j - weight[i]] + value[i])  
      49.             {  
      50.                 f[j] = f[j - weight[i]] + value[i];  
      51.             }  
      52.         }  
      53.     }  
      54.     return f[V];  
      55. }  
      56.   
      57. int main()  
      58. {  
      59.     cout<<Knapsack()<<endl;  
      60.     system("pause");  
      61.     return 1;  
      62. }  
      #include <iostream>
      using namespace std;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[V + 1] = {0};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在不超過背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	int sum = 0;//存儲還未處理物品的總容量
      	int bound = 0;
      	//初始化
      	memset(f,0,sizeof(f));
      	for (int i = 1;i <= N;i++)
      	{
      		sum += weight[i];
      	}
      	
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		//設置下界
      		if (i != 1)
      		{
      			sum -= weight[i - 1];
      		}
      		bound = Max(V - sum,weight[i]);
      
      		for (int j = V;j >= bound;j--) //枚舉背包容量
      		{
      			if (f[j] < f[j - weight[i]] + value[i])
      			{
      				f[j] = f[j - weight[i]] + value[i];
      			}
      		}
      	}
      	return f[V];
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;
      	system("pause");
      	return 1;
      }

       輸出方案

      一般而言,背包問題是要求一個最優值,如果要求輸出這個最優值的方案,可以參照一般動態規劃問題輸出方案的方法:記錄下每個狀態的最優值是由狀態轉移方程的哪一項推出來的,換句話說,記錄下它是由哪一個策略推出來的。便可根據這條策略找到上一個狀態,從上一個狀態接著向前推即可。

      這里我們首先給出01背包的二維狀態轉移方程

      1. f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])  
      f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])

      對于狀態f[i][v],它來自兩種策略,可以是f[i - 1][v],也可以是f[i - 1][v - weight[i]] + cost[i]

      其中,對于第二種情況,就是把物品i放入背包了,這里也是我們要找的情況

      根據狀態轉移方程,我們可以給出兩種實現方法

      1) 借助存儲狀態的數組,直接根據狀態轉移方程倒著推,檢測是否滿足

      1. f[i][v] == f[i - 1][v - weight[i]] + value[i]  
      f[i][v] == f[i - 1][v - weight[i]] + value[i]

      如果滿足,則把第i件物品放入了,此時我們要檢測第i - 1件物品,背包容量為v - weight[i]

      不滿足則表示沒有把第i件物品放入,直接檢測第i - 1件物品,此時背包容量還是v

      注意,這種方法只適用于存儲狀態數組不壓縮的情況。壓縮數組由于數據有覆蓋,不能使用

      代碼

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int N = 3;//物品個數   
      5. const int V = 5;//背包最大容量   
      6. int weight[N + 1] = {0,3,2,2};//物品重量   
      7. int value[N + 1] = {0,5,10,20};//物品價值   
      8.   
      9. int f[N + 1][V + 1] = {{0}};  
      10.   
      11. int Max(int x,int y)  
      12. {  
      13.     return x > y ? x : y;  
      14. }  
      15.   
      16. /* 
      17. 目標:在不超過背包容量的情況下,最多能獲得多少價值 
      18.  
      19. 子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值 
      20.  
      21. 狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
      22.  
      23. 初始化:f數組全設置為0 
      24. */  
      25. int Knapsack()  
      26. {  
      27.     //初始化   
      28.     memset(f,0,sizeof(f));  
      29.     //遞推   
      30.     for (int i = 1;i <= N;i++) //枚舉物品   
      31.     {  
      32.         for (int j = 1;j <= V;j++) //枚舉背包容量   
      33.         {  
      34.             f[i][j] = f[i - 1][j];  
      35.             if (j >= weight[i])  
      36.             {  
      37.                 f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);  
      38.             }  
      39.         }  
      40.     }  
      41.     return f[N][V];  
      42. }  
      43. /* 
      44. 輸出順序:逆序輸出物品編號 
      45. 注意:這里借助狀態數組f[i][v] 
      46. 使用狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
      47. */  
      48. void PrintKnapsack()  
      49. {  
      50.     int i = N;//枚舉物品   
      51.     int j = V;//枚舉空間   
      52.       
      53.     cout<<"加入背包的物品編號:"<<endl;  
      54.     while(i)  
      55.     {  
      56.         if (f[i][j] == f[i - 1][j - weight[i]] + value[i])  
      57.         {  
      58.             /*if不滿足,表示第i件物品沒裝入背包, 
      59.               if條件滿足,表示放入背包了*/  
      60.             cout<<i<<" ";  
      61.             j -= weight[i];//此時容量減少   
      62.         }  
      63.         i--;  
      64.     }  
      65.     cout<<endl;  
      66. }  
      67.   
      68. /* 
      69. 輸出順序:順序輸出物品編號 
      70. 注意:這里借助狀態數組f[i][v] 
      71. 使用狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
      72. */  
      73. void PrintKnapsack_recursion(int i,int j)  
      74. {  
      75.     if (i == 0 || j == 0)  
      76.     {  
      77.         return;  
      78.     }  
      79.     if (f[i][j] == f[i - 1][j - weight[i]] + value[i])  
      80.     {  
      81.         PrintKnapsack_recursion(i - 1,j - weight[i]);  
      82.         cout<<i<<" ";  
      83.     }  
      84. }  
      85.   
      86. int main()  
      87. {  
      88.     cout<<Knapsack()<<endl;  
      89.     PrintKnapsack();  
      90.     PrintKnapsack_recursion(N,V);  
      91.     system("pause");  
      92.     return 1;  
      93. }  
      #include <iostream>
      using namespace std;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[N + 1][V + 1] = {{0}};
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在不超過背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[i][j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	memset(f,0,sizeof(f));
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = 1;j <= V;j++) //枚舉背包容量
      		{
      			f[i][j] = f[i - 1][j];
      			if (j >= weight[i])
      			{
      				f[i][j] = Max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
      			}
      		}
      	}
      	return f[N][V];
      }
      /*
      輸出順序:逆序輸出物品編號
      注意:這里借助狀態數組f[i][v]
      使用狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
      */
      void PrintKnapsack()
      {
      	int i = N;//枚舉物品
      	int j = V;//枚舉空間
      	
      	cout<<"加入背包的物品編號:"<<endl;
      	while(i)
      	{
      		if (f[i][j] == f[i - 1][j - weight[i]] + value[i])
      		{
      			/*if不滿足,表示第i件物品沒裝入背包,
      			  if條件滿足,表示放入背包了*/
      			cout<<i<<" ";
      			j -= weight[i];//此時容量減少
      		}
      		i--;
      	}
      	cout<<endl;
      }
      
      /*
      輸出順序:順序輸出物品編號
      注意:這里借助狀態數組f[i][v]
      使用狀態轉移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
      */
      void PrintKnapsack_recursion(int i,int j)
      {
      	if (i == 0 || j == 0)
      	{
      		return;
      	}
      	if (f[i][j] == f[i - 1][j - weight[i]] + value[i])
      	{
      		PrintKnapsack_recursion(i - 1,j - weight[i]);
      		cout<<i<<" ";
      	}
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;
      	PrintKnapsack();
      	PrintKnapsack_recursion(N,V);
      	system("pause");
      	return 1;
      }

      2) 另外開辟數組,在求解最大收益時做標記位。求解完最大收益后,根據這個新數組倒著推結果

      思想:對于現在這個狀態的位置,它存儲的是該狀態上一位置

      注意:這種方法均適用存儲狀態數組不壓縮 和 壓縮兩種情況

      代碼:

      1. #include <iostream>   
      2. using namespace std;  
      3.   
      4. const int N = 3;//物品個數   
      5. const int V = 5;//背包最大容量   
      6. int weight[N + 1] = {0,3,2,2};//物品重量   
      7. int value[N + 1] = {0,5,10,20};//物品價值   
      8.   
      9. int f[V + 1] = {0};  
      10.   
      11. int G[N + 1][V + 1] = {{0}};//求背包序列   
      12.   
      13. int Max(int x,int y)  
      14. {  
      15.     return x > y ? x : y;  
      16. }  
      17.   
      18. /* 
      19. 目標:在不超過背包容量的情況下,最多能獲得多少價值 
      20.  
      21. 子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值 
      22.  
      23. 狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]} 
      24.  
      25. 初始化:f數組全設置為0 
      26. */  
      27. int Knapsack()  
      28. {  
      29.     //初始化   
      30.     memset(f,0,sizeof(f));  
      31.     memset(G,0,sizeof(G));  
      32.     //遞推   
      33.     for (int i = 1;i <= N;i++) //枚舉物品   
      34.     {  
      35.         for (int j = V;j >= weight[i];j--) //枚舉背包容量   
      36.         {  
      37.             if (f[j] < f[j - weight[i]] + value[i])  
      38.             {  
      39.                 f[j] = f[j - weight[i]] + value[i];  
      40.                 G[i][j] = 1;  
      41.             }  
      42.         }  
      43.     }  
      44.     return f[V];  
      45. }  
      46. /* 
      47. 輸出順序:逆序輸出物品編號 
      48. 注意:這里另外開辟數組G[i][v],標記上一個狀態的位置 
      49. G[i][v] = 1:表示物品i放入背包了,上一狀態為G[i - 1][v - weight[i]] 
      50. G[i][v] = 0:表示物品i沒有放入背包,上一狀態為G[i - 1][v] 
      51. */  
      52. void PrintKnapsack()  
      53. {  
      54.     int i = N;//枚舉物品   
      55.     int j = V;//枚舉空間   
      56.       
      57.     cout<<"加入背包的物品編號:"<<endl;  
      58.     while(i)  
      59.     {  
      60.         if (G[i][j] == 1)  
      61.         {  
      62.             /*if不滿足,表示第i件物品沒裝入背包, 
      63.               if條件滿足,表示放入背包了*/  
      64.             cout<<i<<" ";  
      65.             j -= weight[i];//此時容量減少   
      66.         }  
      67.         i--;  
      68.     }  
      69.     cout<<endl;  
      70. }  
      71.   
      72. /* 
      73. 輸出順序:順序輸出物品編號 
      74. 注意:這里另外開辟數組G[i][v],標記上一個狀態的位置 
      75. G[i][v] = 1:表示物品i放入背包了,上一狀態為G[i - 1][v - weight[i]] 
      76. G[i][v] = 0:表示物品i沒有放入背包,上一狀態為G[i - 1][v] 
      77. */  
      78. void PrintKnapsack_recursion(int i,int j)  
      79. {  
      80.     if (i == 0 || j == 0)  
      81.     {  
      82.         return;  
      83.     }  
      84.     if (G[i][j] == 1)  
      85.     {  
      86.         PrintKnapsack_recursion(i - 1,j - weight[i]);  
      87.         cout<<i<<" ";  
      88.     }  
      89. }  
      90.   
      91. int main()  
      92. {  
      93.     cout<<Knapsack()<<endl;  
      94.     PrintKnapsack();  
      95.     PrintKnapsack_recursion(N,V);  
      96.     system("pause");  
      97.     return 1;  
      98. }  
      #include <iostream>
      using namespace std;
      
      const int N = 3;//物品個數
      const int V = 5;//背包最大容量
      int weight[N + 1] = {0,3,2,2};//物品重量
      int value[N + 1] = {0,5,10,20};//物品價值
      
      int f[V + 1] = {0};
      
      int G[N + 1][V + 1] = {{0}};//求背包序列
      
      int Max(int x,int y)
      {
      	return x > y ? x : y;
      }
      
      /*
      目標:在不超過背包容量的情況下,最多能獲得多少價值
      
      子問題狀態:f[j]:表示前i件物品放入容量為j的背包得到的最大價值
      
      狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
      
      初始化:f數組全設置為0
      */
      int Knapsack()
      {
      	//初始化
      	memset(f,0,sizeof(f));
      	memset(G,0,sizeof(G));
      	//遞推
      	for (int i = 1;i <= N;i++) //枚舉物品
      	{
      		for (int j = V;j >= weight[i];j--) //枚舉背包容量
      		{
      			if (f[j] < f[j - weight[i]] + value[i])
      			{
      				f[j] = f[j - weight[i]] + value[i];
      				G[i][j] = 1;
      			}
      		}
      	}
      	return f[V];
      }
      /*
      輸出順序:逆序輸出物品編號
      注意:這里另外開辟數組G[i][v],標記上一個狀態的位置
      G[i][v] = 1:表示物品i放入背包了,上一狀態為G[i - 1][v - weight[i]]
      G[i][v] = 0:表示物品i沒有放入背包,上一狀態為G[i - 1][v]
      */
      void PrintKnapsack()
      {
      	int i = N;//枚舉物品
      	int j = V;//枚舉空間
      	
      	cout<<"加入背包的物品編號:"<<endl;
      	while(i)
      	{
      		if (G[i][j] == 1)
      		{
      			/*if不滿足,表示第i件物品沒裝入背包,
      			  if條件滿足,表示放入背包了*/
      			cout<<i<<" ";
      			j -= weight[i];//此時容量減少
      		}
      		i--;
      	}
      	cout<<endl;
      }
      
      /*
      輸出順序:順序輸出物品編號
      注意:這里另外開辟數組G[i][v],標記上一個狀態的位置
      G[i][v] = 1:表示物品i放入背包了,上一狀態為G[i - 1][v - weight[i]]
      G[i][v] = 0:表示物品i沒有放入背包,上一狀態為G[i - 1][v]
      */
      void PrintKnapsack_recursion(int i,int j)
      {
      	if (i == 0 || j == 0)
      	{
      		return;
      	}
      	if (G[i][j] == 1)
      	{
      		PrintKnapsack_recursion(i - 1,j - weight[i]);
      		cout<<i<<" ";
      	}
      }
      
      int main()
      {
      	cout<<Knapsack()<<endl;
      	PrintKnapsack();
      	PrintKnapsack_recursion(N,V);
      	system("pause");
      	return 1;
      }

      小結:

      01 背包問題是最基本的背包問題,它包含了背包問題中設計狀態、方程的最基本思想。另外,別的類型的背包問題往往也可以轉換成01 背包問題求解。故一定要仔細體會上面基本思路的得出方法,狀態轉移方程的意義,以及空間復雜度怎樣被優化。

      posted @ 2015-04-06 20:11  柳下_MBX  閱讀(634)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 尤物yw193无码点击进入| 国产精品久久无中文字幕| ww污污污网站在线看com| 四虎成人在线观看免费| 午夜欧美精品久久久久久久| 国产精品高清中文字幕| 亚洲精品日韩中文字幕| 91青青草视频在线观看的| 又湿又紧又大又爽A视频男| 亚洲 成人 无码 在线观看| 国产精品一区二区黄色片| 肥西县| 永久免费av网站可以直接看的| 9久久精品视香蕉蕉| 英超| 国产超碰人人做人人爰| 国产亚洲精品俞拍视频| 亚洲AV成人片不卡无码| 苍井空一区二区三区在线观看| 无码精品人妻一区二区三李一桐| 宝贝腿开大点我添添公视频免| 成人亚洲一级午夜激情网| 99精品高清在线播放| av在线播放观看国产| 国产精品一区二区在线蜜芽tv| 国产综合久久亚洲综合| 国产精品99中文字幕| 青青草成人免费自拍视频| 在线看免费无码的av天堂| 国产AV大陆精品一区二区三区| 亚洲精品一区二区三区不| 国产超碰人人做人人爱| 日夜啪啪一区二区三区| 定远县| 无限看片在线版免费视频大全| 深夜精品免费在线观看| 精品国产乱码久久久人妻| 人妻av一区二区三区av免费| 亚洲成a人片在线观看中 | 国产日韩精品一区二区三区在线| 国产亚洲精品中文字幕|