(代碼是學姐給的一個資料,本身從新編譯理解了一遍)node
1、題目 :ios
分別用蠻力法、動態規劃法、回溯法和分支限界法求解0/1揹包問題。算法
注:0/1揹包問題:給定種物品和一個容量爲的揹包,物品的重量是,其價值爲,揹包問題是如何使選擇裝入揹包內的物品,使得裝入揹包中的物品的總價值最大。其中,每種物品只有所有裝入揹包或不裝入揹包兩種選擇。函數
1)基本思想:測試
對於有n種可選物品的0/1揹包問題,其解空間由長度爲n的0-1向量組成,可用子集數表示。在搜索解空間樹時,深度優先遍歷,搜索每個結點,不管是否可能產生最優解,都遍歷至葉子結點,記錄每次獲得的裝入總價值,而後記錄遍歷過的最大價值。spa
2)代碼:3d
#include <iostream> #include<cstdio> using namespace std; #define N 100 struct goods{ int sign;//物品序號 int wight;//物品重量 int value;//物品價值 }; int n,bestValue,cv,cw,C;//物品數量,價值最大,當前價值,當前重量,揹包容量 int X[N],cx[N];//最終存儲狀態,當前存儲狀態 struct goods goods[N]; int Force(int i){ if(i>n-1){ if(bestValue < cv && cw + goods[i].wight <= C){ for(int k=0;k<n;k++) X[k] = cx[k];//存儲最優路徑 bestValue = cv; } return bestValue; } cw = cw + goods[i].wight; cv = cv + goods[i].value; cx[i] = 1;//裝入揹包 Force(i+1); cw = cw-goods[i].wight; cv = cv-goods[i].value; cx[i] = 0;//不裝入揹包 Force(i+1); return bestValue; } int main() { printf("物品種類n:"); scanf("%d",&n); printf("揹包容量C:"); scanf("%d",&C); for(int i=0;i<n;i++){ printf("物品%d的重量w[%d]及其價值v[%d]:",i+1,i+1,i+1); scanf("%d%d",&goods[i].wight,&goods[i].value); } int sum1 = Force(0); printf("蠻力法求解0/1揹包問題:\nX=["); for(int i=0;i<n;i++){ cout << X[i]<<" "; } printf("] 裝入總價值%d\n",sum1); return 0; }
P.S.蠻力法使用的是遞歸,遞歸的使用常常會寄幾個看不懂T_T,這裏再提一下遞歸的問題(之後不要再老是看不懂啦,這樣會顯得本身很辣雞的有木有!!!)code
force(0),向下運行,到force(1),進入force(1),一直到force(n+1),i>n,return 結果,跳出force(n+1),在force(n)處從跳出的地方繼續向下走,就是進入減減減的環節了,而後繼續向下,仍是同樣,加到n+1時就會跳出來當前的force,調到前一個force,繼續向下,循環進行。blog
3)複雜度分析:排序
蠻力法求解0/1揹包問題的時間複雜度爲:2^n
1)基本思想:
令表示在前個物品中可以裝入容量爲的揹包中的物品的最大值,則能夠獲得以下動態函數:
2)代碼:
#include <iostream> #include<cstdio> #define N 100 #define MAX(a,b) a < b ? b : a using namespace std; struct goods{ int sign;//物品序號 int wight;//物品重量 int value;//物品價值 }; int n,bestValue,cv,cw,C;//物品數量,價值最大,當前價值,當前重量,揹包容量 int X[N],cx[N];//最終存儲狀態,當前存儲狀態 struct goods goods[N]; int KnapSack(int n,struct goods a[],int C,int x[]){ int V[N][10*N]; for(int i = 0; i <= n; i++)//初始化第0列 V[i][0] = 0; for(int j = 0; j <= C; j++)//初始化第0行 V[0][j] = 0; for(int i = 1; i <= n; i++) for(int j = 1; j <= C; j++) if(j < a[i-1].wight) V[i][j] = V[i-1][j]; else V[i][j] = MAX(V[i-1][j],V[i-1][j-a[i-1].wight] + a[i-1].value); for(int i = n,j = C; i > 0; i--){ if(V[i][j] > V[i-1][j]){ x[i-1] = 1; j = j - a[i-1].wight; } else x[i-1] = 0; } return V[n][C]; } int main() { printf("物品種類n:"); scanf("%d",&n); printf("揹包容量C:"); scanf("%d",&C); for(int i = 0; i < n; i++){ printf("物品%d的重量w[%d]及其價值v[%d]:",i+1,i+1,i+1); scanf("%d%d",&goods[i].wight,&goods[i].value); } int sum2 = KnapSack(n,goods,C,X); printf("動態規劃法求解0/1揹包問題:\nX=["); for(int i = 0; i < n; i++) cout<<X[i]<<" ";//輸出所求X[n]矩陣 printf("] 裝入總價值%d\n", sum2); return 0; }
3)複雜度分析:
動態規劃法求解0/1揹包問題的時間複雜度爲:n*C
1)基本思想:
回溯法:爲了不生成那些不可能產生最佳解的問題狀態,要不斷地利用限界函數(bounding function)來處死那些實際上不可能產生所需解的活結點,以減小問題的計算量。這種具備限界函數的深度優先生成法稱爲回溯法。
對於有n種可選物品的0/1揹包問題,其解空間由長度爲n的0-1向量組成,可用子集數表示。在搜索解空間樹時,只要其左兒子結點是一個可行結點,搜索就進入左子樹。當右子樹中有可能包含最優解時就進入右子樹搜索。
2)代碼:
#include <iostream> #include<cstdio> #include<string.h> #include<algorithm> using namespace std; #define N 100 struct goods{ int wight;//物品重量 int value;//物品價值 }; int n,bestValue,cv,cw,C;//物品數量,價值最大,當前價值,當前重量,揹包容量 int X[N],cx[N];//最終存儲狀態,當前存儲狀態 struct goods goods[N]; int BackTrack(int i){ if(i > n-1){ if(bestValue < cv){ for(int k = 0; k < n; k++) X[k] = cx[k];//存儲最優路徑 bestValue = cv; } return bestValue; } if(cw + goods[i].wight <= C){//進入左子樹 cw += goods[i].wight; cv += goods[i].value; cx[i] = 1;//裝入揹包 BackTrack(i+1); cw -= goods[i].wight; cv -= goods[i].value;//回溯,進入右子樹 } cx[i] = 0;//不裝入揹包 BackTrack(i+1); return bestValue; } bool m(struct goods a, struct goods b){ return (a.value/a.wight) > (b.value/b.wight); } int KnapSack3(int n, struct goods a[], int C,int x[N]){ memset(x,0,sizeof(x)); sort(a,a+n,m);//將各物品按單位重量價值降序排列 BackTrack(0); return bestValue; } int main() { printf("物品種類n:"); scanf("%d",&n); printf("揹包容量C:"); scanf("%d",&C); for(int i = 0; i < n; i++){ printf("物品%d的重量w[%d]及其價值v[%d]:",i+1,i+1,i+1); scanf("%d%d",&goods[i].wight,&goods[i].value); } int sum3 = KnapSack3(n,goods,C,X); printf("回溯法求解0/1揹包問題:\nX=["); for(int i = 0; i < n; i++) cout << X[i] <<" ";//輸出所求X[n]矩陣 printf("] 裝入總價值%d\n",sum3); return 0; }
1)基本思想:
分支限界法相似於回溯法,也是在問題的解空間上搜索問題解的算法。通常狀況下,分支限界法與回溯法的求解目標不一樣。回溯法的求解目標是找出解空間中知足約束條件的全部解,而分支限界法的求解目標則是找出知足約束條件的一個解,或是在知足約束條件的解中找出使某一目標函數值達到極大或極小的解,即在某種意義下的最優解。
首先,要對輸入數據進行預處理,將各物品依其單位重量價值從大到小進行排列。
在下面描述的優先隊列分支限界法中,節點的優先級由已裝袋的物品價值加上剩下的最大單位重量價值的物品裝滿剩餘容量的價值和。
算法首先檢查當前擴展結點的左兒子結點的可行性。若是該左兒子結點是可行結點,則將它加入到子集樹和活結點優先隊列中。當前擴展結點的右兒子結點必定是可行結點,僅當右兒子結點知足上界約束時纔將它加入子集樹和活結點優先隊列。當擴展到葉節點時爲問題的最優值。
2)代碼:
#include<iostream> #include<algorithm> using namespace std; #define N 100 //最多可能物體數 struct goods //物品結構體 { int sign; //物品序號 int w; //物品重量 int p; //物品價值 }a[N]; bool m(goods a,goods b) { return (a.p/a.w)>(b.p/b.w); } int max(int a,int b) { return a<b?b:a; } int n,C,bestP=0,cp=0,cw=0; int X[N],cx[N]; struct KNAPNODE //狀態結構體 { bool s1[N]; //當前放入物體 int k; //搜索深度 int b; //價值上界 int w; //物體重量 int p; //物體價值 }; struct HEAP //堆元素結構體 { KNAPNODE *p;//結點數據 int b; //所指結點的上界 }; //交換兩個堆元素 void swap(HEAP &a, HEAP&b) { HEAP temp = a; a = b; b = temp; } //堆中元素上移 void mov_up(HEAP H[], int i) { bool done = false; if(i!=1){ while(!done && i!=1){ if(H[i].b>H[i/2].b){ swap(H[i], H[i/2]); }else{ done = true; } i = i/2; } } } //堆中元素下移 void mov_down(HEAP H[], int n, int i) { bool done = false; if((2*i)<=n){ while(!done && ((i = 2*i) <= n)){ if(i+1<=n && H[i+1].b > H[i].b){ i++; } if(H[i/2].b<H[i].b){ swap(H[i/2], H[i]); }else{ done = true; } } } } //往堆中插入結點 void insert(HEAP H[], HEAP x, int &n) { n++; H[n] = x; mov_up(H,n); } //刪除堆中結點 void del(HEAP H[], int &n, int i) { HEAP x, y; x = H[i]; y = H[n]; n --; if(i<=n){ H[i] = y; if(y.b>=x.b){ mov_up(H,i); }else{ mov_down(H, n, i); } } } //得到堆頂元素並刪除 HEAP del_top(HEAP H[], int&n) { HEAP x = H[1]; del(H, n, 1); return x; } //計算分支節點的上界 void bound( KNAPNODE* node,int M, goods a[], int n) { int i = node->k; float w = node->w; float p = node->p; if(node->w>M){ // 物體重量超過揹包載重量 node->b = 0; // 上界置爲0 }else{ while((w+a[i].w<=M)&&(i<n)){ w += a[i].w; // 計算揹包已裝入載重 p += a[i++].p; // 計算揹包已裝入價值 } if(i<n){ node->b = p + (M - w)*a[i].p/a[i].w; }else{ node -> b = p; } } } //用分支限界法實現0/1揹包問題 int KnapSack4(int n,goodsa[],int C, int X[]) { int i, k = 0; // 堆中元素個數的計數器初始化爲0 int v; KNAPNODE *xnode, *ynode, *znode; HEAP x, y, z, *heap; heap = new HEAP[n*n]; // 分配堆的存儲空間 for( i=0; i<n; i++){ a[i].sign=i; //記錄物體的初始編號 } sort(a,a+n,m); // 對物體按照價值重量比排序 xnode = new KNAPNODE; // 創建父親結點 for( i=0; i<n; i++){ // 初始化結點 xnode->s1[i] = false; } xnode->k = xnode->w = xnode->p = 0; while(xnode->k<n) { ynode = new KNAPNODE; // 創建結點y *ynode = *xnode; //結點x的數據複製到結點y ynode->s1[ynode->k] = true; // 裝入第k個物體 ynode->w += a[ynode->k].w; // 揹包中物體重量累計 ynode->p += a[ynode->k].p; // 揹包中物體價值累計 ynode->k ++; // 搜索深度++ bound(ynode, C, a, n); // 計算結點y的上界 y.b = ynode->b; y.p = ynode; insert(heap, y, k); //結點y按上界的值插入堆中 znode = new KNAPNODE; // 創建結點z *znode = *xnode; //結點x的數據複製到結點z znode->k++; // 搜索深度++ bound(znode, C, a, n); //計算節點z的上界 z.b = znode->b; z.p = znode; insert(heap, z, k); //結點z按上界的值插入堆中 delete xnode; x = del_top(heap, k); //得到堆頂元素做爲新的父親結點 xnode = x.p; } v = xnode->p; for( i=0; i<n; i++){ //取裝入揹包中物體在排序前的序號 if(xnode->s1[i]){ X[a[i].sign] =1 ; }else{ X[a[i].sign] = 0; } } delete xnode; delete heap; return v; //返回揹包中物體的價值 } /*測試以上算法的主函數*/ int main() { goods b[N]; printf("物品種數n: "); scanf("%d",&n); //輸入物品種數 printf("揹包容量C: "); scanf("%d",&C); //輸入揹包容量 for (int i=0;i<n;i++) //輸入物品i的重量w及其價值v { printf("物品%d的重量w[%d]及其價值v[%d]: ",i+1,i+1,i+1); scanf("%d%d",&a[i].w,&a[i].p); b[i]=a[i]; } int sum4=KnapSack4(n,a,C,X);//調用分支限界法求0/1揹包問題 printf("分支限界法求解0/1揹包問題:\nX=[ "); for(i=0;i<n;i++) cout<<X[i]<<" ";//輸出所求X[n]矩陣 printf("] 裝入總價值%d\n",sum4); return 0; }