2020-05-22
所有背包問題實現的例子都是下面這張圖
01背包實現之——窮舉法:
1.我的難點:
(1)在用窮舉法實現代碼的時候,我自己做的時候認為最難的就是怎么將那么多種情況表示出來,一開開始想用for循環進行多次嵌套,但是太麻煩,而且還需要不斷的進行各種標記。我現在的水平實在太菜,然后就在一篇博文中看到一個特別巧妙的枚舉算法,如下所示:
int fun(int x[n]) { int i; for(i=0;i<n;i++) if(x[i]!=1) {x[i]=1; return;} //從遇到的第一位開始,若是0,將其變成1,然后結束for循環,得到一種解法 else x[i]=0; return; //從第一位開始,若是1,將其變成0,然后繼續循環,若再循環的時候遇到0,則將其變為1,結束循環。得到另一種解法。 }
雖然我現在也不知道為什么會這樣,但是確實是個很好的規律,找到這個規律后,就可以很輕松的自己寫出各種排列情況,以后遇到排列的問題,就用這個方法。語言不好描述,上圖片演示(是歪的,湊活看吧。。。):
(2)算法思想:
x[i]的值為0/1,即選或者不選
w[i]的值表示商品i的重量
v[i]的值表示商品的價值
所以這個算法最核心的公式就是
tw=x[1]*w[1]+x[2]*w[2]+.......+x[n]*w[n]
tv=x[1]*w[1]+x[2]*v[2]+......+x[n]*v[n]
tv1:用於存儲當前最優解
limit:背包容量
如果 tw<limit&&tv>tv1 則可以找到最優解
2.代碼實現(借鑒博文)
#include<stdio.h> #include<iostream> using namespace std; #define n 4 void possible_solution(int x[n]){ int i; for(i=0;i<4;i++) //n=4,有2^4-1種解法 if(x[i]!=1) { x[i]=1; return; //從遇到的第一位開始,若是0,將其變成1,然后結束循環,得到一種解法 } else x[i]=0; return;//從第一位開始,若是1,將其變成0,然后繼續循環,若再循環的時候遇到0,則將其變為1,結束循環。得到另一種解法。 } int main(){ int count=0; int w[n]={2,3,4,5},v[n]={3,4,5,6}; int x[n]={0,0,0,0},y[n]={0,0,0,0}; int tw,tv,tv1=0,limit=8; int j; for(j=1;j<=15;j++){ possible_solution(x); count++; for(int i=0;i<4;i++){ cout<<x[i]<<" "; } cout<<endl; tw=x[0]*w[0]+x[1]*w[1]+x[2]*w[2]+x[3]*w[3]; tv=x[0]*v[0]+x[1]*v[1]+x[2]*v[2]+x[3]*v[3]; if(tw<=limit&&tv>tv1){ tv1=tv; y[0]=x[0];y[1]=x[1];y[2]=x[2],y[3]=x[3]; } } cout<<"共有"<<count<<"種解法."<<endl; printf("其中0-1背包問題的最優解為: y=(%d,%d,%d,%d)\n",y[0],y[1],y[2],y[3]); printf("總價值為:%d",tv1); }
3.運行結果:
4.復雜度分析
n個物品的話,就有2^n-1種解,所以其時間復雜度為O(2^n)
01背包問題之——貪心算法:
1.算法思路:
取單位價值量最大的那個物品先裝入背包。所以還算好實現,得到每一個物品的價值量之后,查找最大的價值量的坐標,判斷這個坐標額物品體積是否小於背包的容量,若小於,則裝入背包。否則,繼續循環。
2.代碼實現 法一:
將得到的每個物品的價值量進行排序,得到一個遞減序列。
#include<iostream> #include <iomanip> #define n 4 //物品數列 #define c 8 //背包容量 using namespace std; int w[4]={2.0,3.0,4.0,5.0}; float v[4]={3.0,4.0,5.0,6.0}; float sortBest[4]; //v[i]/w[i] int C(){ for(int i=0;i<4;i++){ sortBest[i]=v[i]/w[i]; //cout<<sortBest[i]<<" "; } cout<<endl; } int Sort(){ for(int i=0;i<4;i++) { int temp; int wtemp; int vtemp; if(sortBest[i+1]>sortBest[i]) { temp=sortBest[i]; sortBest[i]=sortBest[i+1]; sortBest[i+1]=temp; wtemp=w[i]; w[i]=w[i+1]; w[i+1]=wtemp; vtemp=v[i]; v[i]=v[i+1]; v[i+1]=vtemp; } //用來查看排序是否正確 cout<<"w["<<i<<"]="<<w[i]<<" "; cout<<endl; cout<<"v["<<i<<"]="<<v[i]<<" "; cout<<endl; cout<<"sortBest["<<i<<"]="<<sortBest[i]<<endl; } cout<<endl; } int F(){ int c1=c; int result=0; for(int i=0;i<4;i++){ if(w[i]<=c1) result=result+v[i]; c1=c1-w[i]; } cout<<"最優值是:"<<result; } int main() { C(); cout<<"背包重量是:"<<c<<endl; Sort(); F(); return 0; }
代碼實現 法二:
沒有對每個商品的價值量進行排序,直接查找當前價值量的最大值,判斷其是否能夠裝入背包,若能,直接裝入,令當前價值量為0,繼續尋找第二大價值量,不斷循環即可。代碼如下:
#include<iostream> #include <iomanip> #define n 4 //物品數列 #define c 8 //背包容量 using namespace std; float w[4]={2.0,3.0,4.0,5.0}; float v[4]={3.0,4.0,5.0,6.0}; float sortBest[4]; //價值量 v[i]/w[i] int C(){ for(int i=0;i<4;i++){ sortBest[i]=v[i]/w[i]; //cout<<sortBest[i]<<" "; } } int F() { float temp=0; float result=0; float c1=8; //用於改變c的值 for(int i=0;i<4;i++) { //for循環用來得到最大sortBest for(i=0;i<4;i++) { if(temp<sortBest[i]) temp=sortBest[i]; } //cout<<"max(sortBest)="<<temp<<endl; for(i=0;i<4;i++) { if (temp==sortBest[i]) //cout<<"最大sortBest的下標是:"<<i<<endl; sortBest[i]=0; if (w[i]<=c1) result=result+v[i]; c1=c1-w[i]; } } cout<<"結果為:"<<result<<endl; } int main() { cout<<"********貪心算法解決01背包問題,誰的sortBest=v[i]/w[i] 大,就先拿誰***********"<<endl; cout<<"背包的總重量是:"<<c<<endl; cout<<"可挑選的物品共4件"<<endl; cout<<endl; for(int i=0;i<4;i++) { cout<<"w["<<i<<"]="<<w[i]<<" "; cout<<"v["<<i<<"]="<<v[i]<<" "; cout<<"sortBest["<<i<<"]="<<v[i]/w[i]<<" "; cout<<endl; } C(); F(); return 0; }
3.遇到的困難
就是,當得到的價值量的包含小數時,而且剛好就靠小數部分區分大小時(比如1.5 ,1.33,)。c++正常輸出的結果都是整數。
解決辦法就是,將每個物品的價值量(3.0,4.0)和背包重量(2.0,3.0)都變float類型,注意定義的時候,也需要定義為float類型
4.復雜度:
時間復雜度:O(n)
01背包問題之——動態規划
1.算法思想
最重要的就是尋找遞推關系式:
定義V[i,j]:當背包容量為j時,前i個物品最佳組合對應的值。
遞推關系:
(1)當背包的容量不允許裝入第i件物品時,和前一個物品裝入背包一樣。即 :V[i][j]=V[i-1][j]
(2)當背包的容積可以裝入第i件物品時,分兩種情況,A裝入第i件物品不是最優,還不如不裝。B裝入第i件物品是最優。即:V[i][j]=max(V[i-1][j],V[i][j-w[i]]+v[i])
2.代碼實現:
#include<iostream> using namespace std; int w[5]={0,2,3,4,5}; int v[5]={0,3,4,5,6}; int V[5][9]; int c=8; int B() { int i,j; for(i=0;i<5;i++) { V[i][0]=0; for(j=0;j<c+1;j++) { V[0][j]=0; if(j<w[i]) V[i][j]=V[i-1][j]; else V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]); } } } int main(){ B(); //顯示填好的表格 for (int i=0;i<5;i++) { for(int j=0;j<9;j++) { cout<<V[i][j]<<" "; } cout<<endl; } cout<<"最優結果是:"<<V[4][8]; return 0; }
下面是帶上回溯找出解的組成的代碼:
#include<iostream> using namespace std; int w[5]={0,2,3,4,5}; int v[5]={0,3,4,5,6}; int V[5][9]; int c=8; int item[4]; int B() { int i,j; for(i=0;i<5;i++) { V[i][0]=0; for(j=0;j<c+1;j++) { V[0][j]=0; if(j<w[i]) V[i][j]=V[i-1][j]; else V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]); } } } void FindWhat(int i,int j)//尋找解的組成方式 { if(i>=0) { if(V[i][j]==V[i-1][j])//相等說明沒裝 { item[i]=0;//全局變量,標記未被選中 FindWhat(i-1,j); } else if( j-w[i]>=0 && V[i][j]==V[i-1][j-w[i]]+v[i] ) { item[i]=1;//標記已被選中 FindWhat(i-1,j-w[i]);//回到裝包之前的位置 } } } int main(){ B(); //顯示填好的表格 cout<<"得到的表格如下圖所示:"<<endl; for (int i=0;i<5;i++) { for(int j=0;j<9;j++) { cout<<V[i][j]<<" "; } cout<<endl; } cout<<"最優結果是:"<<V[4][8]<<endl; FindWhat(4,8); cout<<endl; cout<<"回溯得到的解是:"<<endl; for(int i=1;i<5;i++){ if(item[i]==1) cout<<"背包里面有第"<<i<<"號物品"<<endl; //cout<<item[i]<<" "; } return 0; }
3.復雜度
時間復雜度:
O(物體個數*背包容積)=O(number*capacity)
空間復雜度:
用二維表實現的,所以和時間復雜度一樣。
O(物體個數*背包容積)=O(number*capacity)
01背包之——遞歸
1.
遞歸法思路很單一,也是在遞歸方程的基礎上,將其改造為可以遞歸的方式
2.代碼演示
#include<iostream> using namespace std; int n=4; int w[4]={2,3,4,5}; int v[4]={3,4,5,6}; int y[4]={0,0,0,0}; int c=8; int f(int n,int c) { int temp1; int temp2; if(n==0||c==0) { return 0;} else { for(int i=n-1;i>=0;i--) { if(w[i]>c) { return f(n-1,c); } else { temp1=f(n-1,c); temp2=f(n-1,c-w[i])+v[i]; if(temp1>temp2) { return temp1; } else if(temp1<temp2) { return temp2; } } } } } int main() { cout<<"最優值為:"<<f(4,8)<<endl; return 0; }
3.我的難點:
因為是遞歸,所以其最大的缺點就是重復計算,所以如果我想查找他的解是什么,不容易查找。因為如果你進行標記的話,因為會重復計算,所以標記的話,標記也是不停的會變。所以我也不知道怎么解決。
4.復雜度:
O(2^n)
01背包之——回溯