C++回溯与分支限界算法分别解决背包问题详解
作者:成就一亿技术人
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?下面我们分别用回溯与分支限界方法解决
算法思想
分支限界法与回溯法的求解目标不同。
回溯法的求解目标是找出解空间中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
由于求解目标不同,导致分支限界法与回溯法对解空间的搜索方式也不相同。
回溯法以深度优先的方式搜索解空间,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间。
回溯法对解空间做深度优先搜索时,有递归回溯和迭代回溯(非递归)两种方法,但一般情况下用递归方法实现回溯法。
常见的两种分支限界法
先进先出(FIFO)队列式:在先进先出的分支限界法中,用队列作为组织活结点表的数据结构,并按照队列先进先出的原则选择结点作为扩展结点。
优先队列(PQ):用优先队列作为组织活结点表的数据结构。
回溯代码
#include<stdio.h> int n,c,bestp;//物品的个数,背包的容量,最大价值 int p[10000],w[10000],x[10000],bestx[10000];//物品的价值,物品的重量,x[i]暂存物品的选中情况,物品的选中情况 void Backtrack(int i,int cp,int cw) { //cw当前包内物品重量,cp当前包内物品价值 int j; if(i>n)//回溯结束 { 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("请输入物品个数:\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; }
回溯结果
分支限界代码
#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; //解空间中的节点数累计,全局变量 struct nodetype //队列中的结点类型 { int no; //结点编号,从1开始 int i; //当前结点在搜索空间中的层次 int w; //当前结点的总重量 int v; //当前结点的总价值 int x[maxn]; //当前结点包含的解向量 double ub; //上界 }; void input() { cout<<"请输入物品的个数:"<<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) //计算分支结点e的上界 { int i=e.i+1; //考虑结点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) //结点e进队qu { if(e.i==n) //到达叶子节点,不在扩展对应一个解 { if(e.v>bestv) //找到更大价值的解 { bestv=e.v; for(int j=1;j<=n;j++) bestx[j]=e.x[j]; } } else qu.push(e); //非叶子结点进队 } 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(); //出队结点e if(e.w+w[e.i+1]<=c) //剪枝,检查左孩子结点 { e1.no=total++; //建立左孩子结点 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); //左孩子结点进队 } 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) //若右孩子结点可行,则进队,否则被剪枝 enqueue(e2,qu); } } void output() { cout<<"最优值是:"<<bestv<<endl; cout<<"("; for(int i=1;i<=n;i++) cout<<bestx[i]<<" "; cout<<")"; } int main() { input(); bfs(); output(); return 0; }
分支限界结果
到此这篇关于C++回溯与分支限界算法分别解决背包问题详解的文章就介绍到这了,更多相关C++背包问题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!