01背包(詳解)


原創

問題描述:
給定N個物品,每個物品有一個重量W和一個價值V.你有一個能裝M重量的背包.問怎么裝使得所裝價值最大.每個物品只有一個.
輸入格式
  輸入的第一行包含兩個整數n, m,分別表示物品的個數和背包能裝重量。
  以后N行每行兩個數Wi和Vi,表示物品的重量和價值
輸出格式
  輸出1行,包含一個整數,表示最大價值。
樣例輸入
3 5
2 3
3 5
4 7
樣例輸出
8
數據規模和約定
  1<=N<=200,M<=5000.
解題思路:
大家應該都看過很多大神的代碼了,如果還有不明白的地方,不妨聽聽我的解釋,希望能給你帶來柳暗花明的感覺。
首先大家都應該知道,一個物品,有放與不放兩種選擇,我們的最終結果就是綜合所有物品放與不放的選擇來確定。
有這樣一個狀態方程:
F[ i ][ j ]=max( F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] );
其實不難理解,F[ i ][ j ]  代表 當放到第 i 個物品時,此時容量還剩 j ,我們可以選擇放或者不放;
不放的話 F[ i ][ j ]=F[ i-1 ][ j ];就是相當於放了前 i-1 個物品中的物品;注意,不一定是前 i-1 個
物品全都放進了背包。第 i 個物品放進去的話 F[ i ][ j ]=F[ i-1 ][ j - wi[i] ] + vi[ i ]; 放進去了所以
在前面放了 i-1 個物品中的物品的基礎上,容量減去 wi[i], 價值加上vi[ i ](但得在wi[i] <= j 條件下)
有些同學估計挺納悶,在放得下的前提下從F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] ) 這兩個中選擇
最大的,那肯定是加入了第 i 個物品的大呀,這還需要思考嗎?請看下圖:
假設有3個物品,重量分別為:9、7、8;價值分別是:10、15、16,背包容量是20.
其實只要我們按照那個公式套進去,就會發現,每個物品放與不放的情況都會被遍歷到,
回到上面的問題,F[ i-1 ][ j ]  < F[ i-1 ][ j - wi[i] ] + vi[ i ] 是不錯,但是你放了第 i 個物品,假設是這個例子
的最后一個物品,前面你或許就得少放一個物品了。也許你放了第 i 個物品,放不下 i+1 個物品了,但是 vi[ i+1 ] > vi[ i ]
所以每種情況我們都要遍歷過,選出最優的。
大家請看上圖,思路就是從左上角開始,一行一行的求出所有放與不放的可能,右下角的44就是我們要求的答案了;但是需要注意的是,
其實很多數據我們根本用不着,從最后一行往上數,我們要的數據個數只是1、2、4、8、16個而已。
代碼:
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<string.h>
 4  
 5 int max(int a,int b)
 6 {
 7     return a>b?a:b;
 8 }
 9  
10 int main()
11 {
12     int n,m;
13     scanf("%d%d",&n,&m);
14     
15     int *w,*v;
16     w=(int *)malloc(sizeof(int)*n);
17     v=(int *)malloc(sizeof(int)*n); 
18     
19     int **arr;    //動態分配二維數組 
20     arr=(int **)malloc(sizeof(int *)*(n+1));
21     int i;
22     for(i=0;i<=n;i++)
23         arr[i]=(int *)malloc(sizeof(int)*(m+1));
24     
25     int j;    
26     for(i=0;i<=n-1;i++)
27         scanf("%d%d",&w[i],&v[i]);
28     for(i=0;i<=n;i++)
29         for(j=0;j<=m;j++)
30         {
31             if(i==0 || j==0)    //0容量或者0物品置0 
32                 arr[i][j]=0;
33             else if(w[i-1]<=j)
34                 arr[i][j]=max(arr[i-1][j],arr[i-1][j-w[i-1]]+v[i-1]);
35             else
36                 arr[i][j]=arr[i-1][j];
37         }
38     printf("%d",arr[n][m]);
39     free(arr);
40     free(w);
41     free(v);
42     return 0;
43 }
View Code

思路提升:

從上面的表格可以看到,我們用了一個二維數組存儲了每一行的數據,但是實際情況是第1行數據只是第2行用,用完以后就沒用了,

第 i 行的數據只是第 i+1 行使用,用完了就放在數組里面占用大量空間了;所以我們完全可以用一個 “滾動數組” 來實現這種操作,

所謂的滾動數組其實很簡單,就是數組里面我們先存放第 1 行的數據,然后我們根據第 1 行的數據求出第 2 行的數據存放在數組;

就是第 i 行的數據先存放,我求出第 i+1 行的數據以后數組我來占用,這樣不斷變化的數組就是滾動數組,很簡單吧。

代碼實現:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 int max(int a,int b)
 5 {
 6     return a>b?a:b;
 7 }
 8 
 9 int main()
10 {
11     int n,m;
12     scanf("%d%d",&n,&m);
13     
14     int *wi,*vi;
15     wi=(int *)malloc(sizeof(int)*(n+1));
16     vi=(int *)malloc(sizeof(int)*(n+1));
17     
18     int i;
19     for(i=1;i<=n;i++)
20         scanf("%d%d",&wi[i],&vi[i]);
21         
22     int *dp;
23     dp=(int *)malloc(sizeof(int)*(m+1));    //滾動數組 
24     for(i=0;i<=m;i++)
25         dp[i]=0;
26     
27     int j;
28     for(i=0;i<=n;i++)
29         for(j=m;j>=wi[i];j--)    //容量從大到小 
30         {
31             if(i==0)
32             { 
33                 dp[j]=0;
34                 continue;
35             }    
36             
37             dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
38         }
39     
40     printf("%d",dp[m]);
41     return 0;
42 } 
View Code

代碼中狀態方程變為:dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);

請大家注意,dp[j]代表的是現在要求的第 i 個物品考慮放不放在容量為 j 的物品下的最優解,而括號里面的dp[j]代表的

是放 i-1 個物品中的物品時的最優解,相當於上面的F[ i-1 ][ j ],也就是說這個是前一個狀態的量,而dp[j-wi[i]]+vi[i]

就是上面的F[ i-1 ][ j - wi[i] ] + vi[ i ]了。但是容量 j 的值要從大到小了,因為我們求大容量背包的價值時要用到

小容量背包的價值,所以如果從小容量開始求,等到求放下一個物品時,從小容量開始求,那么當我們求這個物品的大容量時放與不放時

上一個物品的小容量值由於被這個物品的小容量值覆蓋,所以要從大容量開始求。

2018-04-06


免責聲明!

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



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