C++回溯與分支限界算法分別解決背包問題詳解
算法思想
分支限界法與回溯法的求解目標不同。
回溯法的求解目標是找出解空間中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出使某一目標函數(shù)值達到極大或極小的解,即在某種意義下的最優(yōu)解。
由于求解目標不同,導致分支限界法與回溯法對解空間的搜索方式也不相同。
回溯法以深度優(yōu)先的方式搜索解空間,而分支限界法則以廣度優(yōu)先或以最小耗費優(yōu)先的方式搜索解空間。
回溯法對解空間做深度優(yōu)先搜索時,有遞歸回溯和迭代回溯(非遞歸)兩種方法,但一般情況下用遞歸方法實現(xiàn)回溯法。
常見的兩種分支限界法
先進先出(FIFO)隊列式:在先進先出的分支限界法中,用隊列作為組織活結(jié)點表的數(shù)據(jù)結(jié)構(gòu),并按照隊列先進先出的原則選擇結(jié)點作為擴展結(jié)點。
優(yōu)先隊列(PQ):用優(yōu)先隊列作為組織活結(jié)點表的數(shù)據(jù)結(jié)構(gòu)。
回溯代碼
#include<stdio.h> int n,c,bestp;//物品的個數(shù),背包的容量,最大價值 int p[10000],w[10000],x[10000],bestx[10000];//物品的價值,物品的重量,x[i]暫存物品的選中情況,物品的選中情況 void Backtrack(int i,int cp,int cw) { //cw當前包內(nèi)物品重量,cp當前包內(nèi)物品價值 int j; if(i>n)//回溯結(jié)束 { if(cp>bestp) { bestp=cp; for(i=0;i<=n;i++) bestx[i]=x[i]; } } else for(j=0;j<=1;j++) { x[i]=j; if(cw+x[i]*w[i]<=c) { cw+=w[i]*x[i]; cp+=p[i]*x[i]; Backtrack(i+1,cp,cw); cw-=w[i]*x[i]; cp-=p[i]*x[i]; } } } int main() { int i; bestp=0; printf("請輸入背包最大容量:\n"); scanf("%d",&c); printf("請輸入物品個數(shù):\n"); scanf("%d",&n); printf("請依次輸入物品的重量:\n"); for(i=1;i<=n;i++) scanf("%d",&w[i]); printf("請依次輸入物品的價值:\n"); for(i=1;i<=n;i++) scanf("%d",&p[i]); Backtrack(1,0,0); printf("最大價值為:\n"); printf("%d\n",bestp); printf("被選中的物品依次是(0表示未選中,1表示選中)\n"); for(i=1;i<=n;i++) printf("%d ",bestx[i]); printf("\n"); return 0; }
回溯結(jié)果
分支限界代碼
#include<iostream> #include<queue> using namespace std; const int maxn=99; int n,c; int w[maxn]; int v[maxn]; int bestv=0; int bestx[maxn]; int total=1; //解空間中的節(jié)點數(shù)累計,全局變量 struct nodetype //隊列中的結(jié)點類型 { int no; //結(jié)點編號,從1開始 int i; //當前結(jié)點在搜索空間中的層次 int w; //當前結(jié)點的總重量 int v; //當前結(jié)點的總價值 int x[maxn]; //當前結(jié)點包含的解向量 double ub; //上界 }; void input() { cout<<"請輸入物品的個數(shù):"<<endl; cin>>n; cout<<"請輸入每個物品的重量及價值(如5 4):"<<endl; for(int i = 1; i <= n; i++) { cin>>w[i]>>v[i]; } cout<<"請輸入背包的容量:"<<endl; cin>>c; } void bound(nodetype &e) //計算分支結(jié)點e的上界 { int i=e.i+1; //考慮結(jié)點e的余下物品 int sumw=e.w; double sumv=e.v; while((sumw+w[i]<=c)&&i<=n) { sumw+=w[i]; sumv+=v[i]; i++; } if(i<=n) //余下物品只能部分裝入 e.ub=sumv+(c-sumw)*v[i]/w[i]; else e.ub=sumv; } void enqueue(nodetype e,queue<nodetype> &qu) //結(jié)點e進隊qu { if(e.i==n) //到達葉子節(jié)點,不在擴展對應一個解 { if(e.v>bestv) //找到更大價值的解 { bestv=e.v; for(int j=1;j<=n;j++) bestx[j]=e.x[j]; } } else qu.push(e); //非葉子結(jié)點進隊 } void bfs() { int j; nodetype e,e1,e2; queue<nodetype> qu; e.i=0; e.w=0; e.v=0; e.no=total++; for(j=1;j<=n;j++) e.x[j]=0; bound(e); qu.push(e); while(!qu.empty()) { e=qu.front();qu.pop(); //出隊結(jié)點e if(e.w+w[e.i+1]<=c) //剪枝,檢查左孩子結(jié)點 { e1.no=total++; //建立左孩子結(jié)點 e1.i=e.i+1; e1.w=e.w+w[e1.i]; e1.v=e.v+v[e1.i]; for(j=1;j<=n;j++) e1.x[j]=e.x[j]; e1.x[e1.i]=1; bound(e1); //求左孩子的上界 enqueue(e1,qu); //左孩子結(jié)點進隊 } e2.no=total++; e2.i=e.i+1; e2.w=e.w; e2.v=e.v; for(j=1;j<=n;j++) e2.x[j]=e.x[j]; e2.x[e2.i]=0; bound(e2); if(e2.ub>bestv) //若右孩子結(jié)點可行,則進隊,否則被剪枝 enqueue(e2,qu); } } void output() { cout<<"最優(yōu)值是:"<<bestv<<endl; cout<<"("; for(int i=1;i<=n;i++) cout<<bestx[i]<<" "; cout<<")"; } int main() { input(); bfs(); output(); return 0; }
分支限界結(jié)果
到此這篇關(guān)于C++回溯與分支限界算法分別解決背包問題詳解的文章就介紹到這了,更多相關(guān)C++背包問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!