当前位置:   article > 正文

优先队列式分支限界法解01背包_优先队列式分支限界法求解0-1背包问题时,计算结点上界值时所用算法思想

优先队列式分支限界法求解0-1背包问题时,计算结点上界值时所用算法思想

概念

  分支限界采用的是广搜。

  优先队列采用的是队列里最优的出队。(这里采用最大堆来实现活结点优先队列,最大堆以活结点的界值作为优先级)

说明:

    对于优先队列式分支限界法解01背包,实际上是广搜遍历生成树的过程。因为01背包。背包只有选和不选。所以该生成树是一个二叉树。假设规定左叉标1(代表选择该物品装入背包),右叉标0(代表不选择该物品装入背包)。给定示例输入:

背包容量c=10

物品个数n=5

物品重量w={2,2,6,5,4}

物品价格p={6,3,5,4,6}

步骤:

    左子树的解的上界与父节点相同,不用计算。右子树的解的界值:较好的就算方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直到装不下时,再装入该物品的一部分来装满背包。由此得到的价值是右子树中解的上界(尽管这不是一个可行解,但可以证明其价值是最优值的上界)。

1.排好序的情况:

物品重量w={2,2,4,6,5}

物品价格p={6,3,6,5,4}

2.一层一层的将活结点加入到活结点优先队列中:

说明:

    0.程序先是将1,2结点加入到队列中,由于1的界大于2的 界,所以选择1结点继续扩展(继续向下搜索)---进入到3,4结点的选择。由于父节点2没有出队。所以相继的5,6结点就被沉默掉了,故第二层只考虑3,4结点,3选择了2号物品后没有超重,4的界也大于当前背包的价格6+3=9.所以3,4都被加入到活结点,又由于3的界大于4的界,所以选择3作为扩展结点,继续向下进行扩展。。。。。。直到叶子结点。。。。程序结束

    1.该生成树中:只要左儿子结点没超重就把他加入到活结点优先队列中来。只要右儿子结点的界值大于当前背包的价值,就将右儿子结点也加入到活结点优先队列中来。当进入到下一层的搜索时,会从上一层的活结点队列中选择最优的那个结点(界值最大的)作为父节点(扩展结点)进行扩展,向下搜索。而上一层没有机会出队的活结点将暂时被沉默掉,等待出队的机会

   2.下面再附上MaxKanpsack()函数的运行数据变化图。方便更快速的理解代码。

      注解:下图中:i=4,  a'''=add(14>10),add其实是addlivenode()函数,这里的14表示当前结点下(选择1,2,3,4号物品后背包重量为14=2+2+4+6,明显大于了背包的重量10,所以a'''=add()也应该划叉,即该结点被终结了,因为超重了。下文的i=5,a'''=add(13>10)分析同上。)

     各个节点界的计算:1结点:(6+3+6)+(10-2-2-4)*(5/6)=16.6666            

                                     2结点:(3+6)+(10-2-4)*(5/6)=12.3333         ...........................

                                     8结点:  (6+3)+(10-2-2)*(5/6)=14.00000

                                     16结点: (6+3+6)+(10-2-2-4)*(4/5)=16.60000

                                     34结点: (6+3+6)+(10-2-2-4)* 0=15

                                     左儿子结点的界不用算,直接继承父节点的界(左儿子的界=父节点的界)3结点的界=1结点的界=7结点的界=16.66

                    

过程说明:i=1时:第一层广搜:1和2结点作比较。由于1和2的界(16.6,12.3)都大于当前背包的价值6,故都加入活结点的优先队列中。又1的 界大于2的界,选择1作扩展结点(父节点)进入下一层。............

                i=3时,背包价格:15=6+3+6;而8号的界为14<15(8号界小于当前背包重量,不加入活结点优先队列中),15号结点对应的背包重量是2+2+4+6=14>10(背包重量),超重了。

代码如下:

  1. #include<stdio.h>
  2. #include <iostream>
  3. using namespace std;
  4. typedef int Typew;
  5. //typedef int Typep;
  6. //物品类
  7. class Object{
  8. friend float Knapsack(Typew *, float *, Typew, int, int *);
  9. public:
  10. int operator <= (Object a) const{
  11. return (d >= a.d);
  12. }
  13. private:
  14. int ID; //物品编号
  15. float d; //单位重量价值
  16. };
  17. //树结点类
  18. class bbnode{
  19. friend class Knap;
  20. friend float Knapsack(Typew *, float *, Typew, int, int *);
  21. private:
  22. bbnode *parent; //指向父节点的指针
  23. int LChild; //如果是左儿子结点取1,也即说明该物品已装进背包
  24. };
  25. //堆结点类
  26. class HeapNode{
  27. friend class Knap;
  28. friend class MaxHeap;
  29. public:
  30. operator float()const{return uprofit;};
  31. private:
  32. float uprofit, //结点的价值上界
  33. profit; //结点所相应的价值
  34. Typew weight; //结点所相应的重量
  35. int level; //活结点在子集树中所处的层序号
  36. bbnode *elemPtr; //指向该活结点在子集树中相应结点的指针
  37. };
  38. //最大堆类
  39. class MaxHeap{
  40. public:
  41. MaxHeap(int maxElem)
  42. {
  43. HeapElem = new HeapNode* [maxElem+1]; //下标为0的保留
  44. capacity = maxElem;
  45. size = 0;
  46. }
  47. void InsertMax(HeapNode *newNode);
  48. HeapNode DeleteMax(HeapNode* &N);
  49. private:
  50. int capacity;
  51. int size;
  52. HeapNode **HeapElem;
  53. };
  54. //0-1背包问题的主类
  55. class Knap{
  56. //Knapsack主函数功能:解决初始化、求解最优值和最优解、回收内存
  57. friend float Knapsack(Typew *, float *, Typew, int, int *);
  58. public:
  59. float MaxKnapsack();
  60. private:
  61. MaxHeap *H;
  62. //Bound辅助Maxknapsack函数:计算结点价值上界
  63. float Bound(int i);
  64. //AddLiveNode辅助Maxknapsack函数:将活结点插入子集树和优先队列中
  65. void AddLiveNode(float up, float cp, Typew cw, int ch, int level);
  66. bbnode *E; //指向扩展结点的指针
  67. Typew c; //背包容量
  68. int n; //物品总数
  69. Typew *w; //物品重量数组(以单位重量价值降序)
  70. float *p; //物品价值数组(以单位重量价值降序)
  71. Typew cw; //当前装包重量
  72. float cp; //当前装包价值
  73. int *bestx; //最优解
  74. };
  75. //这是博客上大牛自己写的,原教材上没有
  76. void MaxHeap::InsertMax(HeapNode *newNode)
  77. {
  78. //极端情况下暂未考虑,比如堆容量已满等等
  79. int i = 1;
  80. for (i = ++size; i/2 > 0 && HeapElem[i/2]->uprofit < newNode->uprofit; i /= 2)
  81. {
  82. HeapElem[i] = HeapElem[i/2];
  83. }
  84. HeapElem[i] = newNode;
  85. }
  86. //这是博客上大牛自己写的,原教材上没有
  87. HeapNode MaxHeap::DeleteMax(HeapNode *&N)
  88. {
  89. //极端情况下暂未考虑
  90. if(size >0 )
  91. {
  92. N = HeapElem[1];
  93. //从堆顶开始调整
  94. int i = 1;
  95. while(i < size)
  96. {
  97. if(((i*2 +1) <= size) && HeapElem[i*2]->uprofit > HeapElem[i*2 +1]->uprofit)
  98. {
  99. HeapElem[i] = HeapElem[i*2];
  100. i = i*2;
  101. }
  102. else
  103. {
  104. if(i*2 <= size)
  105. {
  106. HeapElem[i] = HeapElem[i*2];
  107. i = i*2;
  108. }
  109. else
  110. break;
  111. }
  112. }
  113. if(i < size)
  114. HeapElem[i] = HeapElem[size];
  115. }
  116. size--;
  117. return *N;
  118. }
  119. float Knap::MaxKnapsack()
  120. {
  121. H = new MaxHeap(1000);
  122. bestx = new int [n+1];
  123. //初始化,为处理子集树中的第一层做准备,物品i处于子集树中的第i层
  124. int i = 1; //生成子集树中的第一层的结点
  125. E = 0; //将首个扩展点设置为null,也就是物品1的父节点
  126. cw = 0;
  127. cp = 0;
  128. float bestp = 0; //当前最优值
  129. float up = Bound(1); // 选取物品1之后的价值上界
  130. //当选择左儿子结点时,上界约束up不用关心,重量约束wt需要考虑。因为上界约束跟父节点相同。
  131. //当选择右儿子结点时,上界约束up需要考虑,重量约束不需要考虑。因为父节点和该结点重量相同。
  132. while (i != n+1)
  133. {
  134. //检查当前扩展结点的左儿子结点
  135. int wi=w[i];
  136. int pi=p[i];
  137. int cp1=cp;
  138. int cw1=cw;
  139. int test=cw1+cp1;
  140. Typew wt = cw + w[i]; //当前选择物品i之后的总重量wt
  141. if(wt <= c) //背包能将物品i装下,也即当前扩展结点的左儿子结点可行
  142. {
  143. if(cp + p[i] > bestp)
  144. bestp = cp +pi;
  145. //bestp = cp + p[i]
  146. AddLiveNode(up, cp + p[i], cw + w[i], 1, i);
  147. }
  148. //检查当前扩展结点的右儿子结点
  149. up = Bound(i + 1); //未选择物品i之后的价值上界
  150. if(up >= bestp)
  151. AddLiveNode(up, cp, cw, 0, i);
  152. //从优先队列中选择价值上界最大的结点成为扩展结点
  153. HeapNode* N;
  154. H->DeleteMax(N);
  155. E = N->elemPtr;
  156. cw = N->weight;
  157. cp = N->profit;
  158. up = N->uprofit;
  159. i = N->level + 1; //准备生成N.level+1层的子集树结点
  160. }
  161. //从子集树中的某叶子结点开始构造当前最优解
  162. for (int i = n; i > 0; i--)
  163. {
  164. bestx[i] = E->LChild;
  165. E = E->parent;
  166. }
  167. return cp;
  168. }
  169. float Knap::Bound(int i)
  170. {
  171. Typew cleft = c - cw;
  172. float b = cp;
  173. while (i<=n && w[i] <= cleft)
  174. {
  175. cleft -= w[i];
  176. b += p[i];
  177. i++;
  178. }
  179. if(i<=n) b += p[i]/w[i] * cleft;
  180. return b;
  181. }
  182. void Knap::AddLiveNode(float up, float cp, Typew cw, int ch, int level)
  183. {
  184. bbnode *b=new bbnode;
  185. b->parent=E;
  186. b->LChild=ch;
  187. HeapNode *N = new HeapNode;
  188. N->uprofit=up;
  189. N->profit=cp;
  190. N->weight=cw;
  191. N->level=level;
  192. N->elemPtr=b;
  193. H->InsertMax(N);
  194. }
  195. //Knapsack返回最大价值,最优值保存在bestx
  196. float Knapsack(Typew *w, float *p, Typew c, int n, int *bestx)
  197. {//数组w、p和bestx中下标为0的元素保留不用
  198. //初始化
  199. Typew W = 0;
  200. float P = 0;
  201. Object *Q = new Object[n];
  202. for(int i =1; i<=n; i++)
  203. {
  204. Q[i-1].ID = i;
  205. Q[i-1].d = 1.0*p[i]/w[i];
  206. P += p[i];
  207. W += w[i];
  208. }
  209. //所有物品的总重量小于等于背包容量c
  210. if (W <= c)
  211. {
  212. for(int i =1; i<=n; i++)
  213. {
  214. bestx[i] = p[i];
  215. }
  216. return P;
  217. }
  218. //所有物品的总重量大于背包容量c,存在最佳装包方案
  219. //sort(Q,n);对物品以单位重量价值降序排序
  220. //1.对物品以单位重量价值降序排序
  221. //采用简单冒泡排序
  222. for(int i = 1; i<n; i++)
  223. for(int j = 1; j<= n-i; j++)
  224. {
  225. if(Q[j-1].d < Q[j].d)
  226. {
  227. Object temp = Q[j-1];
  228. Q[j-1] = Q[j];
  229. Q[j] = temp;
  230. }
  231. }
  232. //Knapsack主函数功能:解决初始化、求解最优值和最优解、回收内存
  233. Knap K;
  234. K.p = new float [n+1];
  235. K.w = new Typew [n+1];
  236. for(int i = 1; i<=n; i++)
  237. {
  238. K.p[i] = p[Q[i-1].ID];//(以单位重量价值降序排序)
  239. K.w[i] = w[Q[i-1].ID];//(以单位重量价值降序排序)
  240. }
  241. K.cp = 0;
  242. K.cw = 0;
  243. K.c = c;
  244. K.n = n;
  245. //2.MaxKnapsack()负责分支限界的搜索,并找出最优值------这是一个填二叉树的过程
  246. float bestp = K.MaxKnapsack();
  247. for(int i = 1; i<=n; i++)
  248. {
  249. bestx[Q[i-1].ID] = K.bestx[i];
  250. }
  251. delete [] Q;
  252. delete [] K.w;
  253. delete [] K.p;
  254. delete [] K.bestx;
  255. delete [] K.H;
  256. return bestp;
  257. }
  258. int main(int argc, char* argv[])
  259. {
  260. const int N = 5;
  261. Typew c=10; //背包容量
  262. int bestx[N+1]; //最优解
  263. int bestp; //最优值
  264. //需要说明的是,这里的数组p[]和w[]的第一个元素是-1,这是因为我在操作过程中
  265. //都是从数组元素的1开始的,而我们知道数组中第一个元素是0号元素,所以我这里用-1填上
  266. Typew w[]={0,2,2,6,5,4};//物品重量
  267. float p[]={0,6,3,5,4,6};//物品价值
  268. //排好序之后的:
  269. //p【6,3,6,5,4】
  270. //w【2,2,4,6,5】
  271. bestp = Knapsack(w, p, c, N, bestx);
  272. cout<<"物品总数N = "<< N<<",背包容量C = "<< c<<endl;
  273. for (int i = 1; i <= N; i++)
  274. {
  275. if(i ==1 ) cout<<"重量数组:";
  276. cout<<w[i];
  277. if(i != N) cout<<",";
  278. else
  279. cout<<endl;
  280. }
  281. for (int i = 1; i <= N; i++)
  282. {
  283. if(i ==1 ) cout<<"价值数组:";
  284. cout<<p[i];
  285. if(i != N) cout<<",";
  286. else
  287. cout<<endl;
  288. }
  289. for (int i = 1; i <= N; i++)
  290. {
  291. if(i ==1 ) cout<<"是否选取:";
  292. cout<<bestx[i];
  293. if(i != N) cout<<",";
  294. else
  295. cout<<endl;
  296. }
  297. cout<<"背包最优价值:"<<bestp<<endl;
  298. system("pause");
  299. return 0;
  300. }

上述代码对于w={3,4,5}    p={4,5,6}  c=6  执行的结果有误

再附上修正后的:

  1. #ifndef BEIBAO_H
  2. #define BEIBAO_H
  3. #include <math.h>
  4. #include <iostream>
  5. using namespace std;
  6. //子空间中节点类型
  7. class BBnode{
  8. public:
  9. BBnode* parent; //父节点
  10. bool leftChild; //左儿子节点标志
  11. BBnode(BBnode* par,bool ch){
  12. parent=par;
  13. leftChild=ch;
  14. }
  15. BBnode(){
  16. }
  17. };
  18. class HeapNode {
  19. public:
  20. BBnode* liveNode; // 活结点
  21. double upperProfit; //结点的价值上界
  22. double profit; //结点所相应的价值
  23. double weight; //结点所相应的重量
  24. int level; // 活结点在子集树中所处的层次号
  25. //构造方法
  26. HeapNode(BBnode* node, double up, double pp , double ww,int lev){
  27. liveNode = node;
  28. upperProfit = up;
  29. profit = pp;
  30. weight = ww;
  31. level = lev;
  32. }
  33. HeapNode(){
  34. }
  35. int compareTo(HeapNode o) {
  36. double xup =o.upperProfit;
  37. if(upperProfit < xup)
  38. return -1;
  39. if(upperProfit == xup)
  40. return 0;
  41. else
  42. return 1;
  43. }
  44. };
  45. class Element {
  46. public:
  47. int id;
  48. double d;
  49. Element(){
  50. }
  51. Element(int idd,double dd){
  52. id=idd;
  53. d=dd;
  54. }
  55. int compareTo(Element x){
  56. double xd=x.d;
  57. if(d<xd)return -1;
  58. if(d==xd)return 0;
  59. return 1;
  60. }
  61. bool equals(Element x){
  62. return d==x.d;
  63. }
  64. };
  65. class MaxHeap{
  66. public:
  67. HeapNode *nodes;
  68. int nextPlace;
  69. int maxNumber;
  70. MaxHeap(int n){
  71. maxNumber = pow((double)2,(double)n);
  72. nextPlace = 1;//下一个存放位置
  73. nodes = new HeapNode[maxNumber];
  74. }
  75. MaxHeap(){
  76. }
  77. void put(HeapNode node){
  78. nodes[nextPlace] = node;
  79. nextPlace++;
  80. heapSort(nodes);
  81. }
  82. HeapNode removeMax(){
  83. HeapNode tempNode = nodes[1];
  84. nextPlace--;
  85. nodes[1] = nodes[nextPlace];
  86. heapSort(nodes);
  87. return tempNode;
  88. }
  89. void heapAdjust(HeapNode * nodes,int s,int m){
  90. HeapNode rc = nodes[s];
  91. for(int j=2*s;j<=m;j*=2){
  92. if(j<m&&nodes[j].upperProfit<nodes[j+1].upperProfit)
  93. ++j;
  94. if(!(rc.upperProfit<nodes[j].upperProfit))
  95. break;
  96. nodes[s] = nodes[j];
  97. s = j;
  98. }
  99. nodes[s] = rc;
  100. }
  101. void heapSort(HeapNode * nodes){
  102. for(int i=(nextPlace-1)/2;i>0;--i){
  103. heapAdjust(nodes,i,nextPlace-1);
  104. }
  105. }
  106. } ;
  107. #endif
  108. //子空间中节点类型
  109. double c=10;
  110. const int n=5;
  111. double *w;
  112. double *p;
  113. double cw;
  114. double cp;
  115. int *bestX;
  116. MaxHeap * heap;
  117. //上界函数bound计算结点所相应价值的上界
  118. double bound(int i){
  119. double cleft=c-cw;
  120. double b=cp;
  121. while(i<=n&&w[i]<=cleft){
  122. cleft=cleft-w[i];
  123. b=b+p[i];
  124. i++;
  125. }
  126. //装填剩余容量装满背包
  127. if(i<=n)
  128. b=b+p[i]/w[i]*cleft;
  129. return b;
  130. }
  131. //addLiveNode将一个新的活结点插入到子集树和优先队列中
  132. void addLiveNode(double up,double pp,double ww,int lev,BBnode* par,bool ch){
  133. //将一个新的活结点插入到子集树和最大堆中
  134. BBnode *b=new BBnode(par,ch);
  135. HeapNode node =HeapNode(b,up,pp,ww,lev);
  136. heap->put(node);
  137. }
  138. double MaxKnapsack(){
  139. //优先队列式分支限界法,返回最大价值,bestx返回最优解
  140. BBnode * enode=new BBnode();
  141. int i=1;
  142. double bestp=0;//当前最优值
  143. double up=bound(1);//当前上界
  144. while(i!=n+1){//非叶子结点
  145. //检查当前扩展结点的左儿子子结点
  146. double wt=cw+w[i];
  147. if(wt<=c){
  148. if(cp+p[i]>bestp)
  149. bestp=cp+p[i];
  150. addLiveNode(up,cp+p[i],cw+w[i],i+1,enode,true);
  151. }
  152. up=bound(i+1);
  153. if(up>=bestp)
  154. addLiveNode(up,cp,cw,i+1,enode,false);
  155. HeapNode node =heap->removeMax();
  156. enode=node.liveNode;
  157. cw=node.weight;
  158. cp=node.profit;
  159. up=node.upperProfit;
  160. i=node.level;
  161. }
  162. for(int j=n;j>0;j--){
  163. bestX[j]=(enode->leftChild)?1:0;
  164. enode=enode->parent;
  165. }
  166. return cp;
  167. }
  168. double knapsack(double *pp,double *ww,double cc,int *xx){
  169. //返回最大值,bestX返回最优解
  170. c=cc;
  171. //n=sizeof(pp)/sizeof(double);
  172. //定义以单位重量价值排序的物品数组
  173. Element *q=new Element[n];
  174. double ws=0.0;
  175. double ps=0.0;
  176. for(int i=0;i<n;i++){
  177. q[i]=Element(i+1,pp[i+1]/ww[i+1]);
  178. ps=ps+pp[i+1];
  179. ws=ws+ww[i+1];
  180. }
  181. if(ws<=c){
  182. return ps;
  183. }
  184. p=new double[n+1];
  185. w=new double[n+1];
  186. for(int i=0;i<n;i++){
  187. p[i+1]=pp[q[i].id];
  188. w[i+1]=ww[q[i].id];
  189. }
  190. cw=0.0;
  191. cp=0.0;
  192. bestX = new int[n+1];
  193. heap = new MaxHeap(n);
  194. double bestp = MaxKnapsack();
  195. for(int j=0;j<n;j++)
  196. xx[q[j].id]=bestX[j+1];
  197. return bestp;
  198. }
  199. int main(){
  200. //w=new double[4];
  201. double w[6]={0,2,2,6,5,4};
  202. double p[6]={0,6,3,5,4,6};
  203. int *x = new int[4];
  204. double m = knapsack(p,w,c,x);
  205. cout<<"*****分支限界法*****"<<endl;
  206. cout<<"*****物品个数:n="<<n<<endl;
  207. cout<<"*****背包容量:c="<<c<<endl;
  208. cout<<"*****物品重量数组:w= {"<<w[3]<<" "<<w[1]<<" "<<w[2]<<"}"<<endl;
  209. cout<<"*****物品价值数组:v= {"<<p[3]<<" "<<p[1]<<" "<<p[2]<<"}"<<endl;
  210. cout<<"*****最优值:="<<m<<endl;
  211. cout<<"*****选中的物品是:";
  212. for(int i=1;i<=3;i++)
  213. cout<<x[i]<<" ";
  214. cout<<endl;
  215. return 0;
  216. }

图片:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/333131
推荐阅读
相关标签
  

闽ICP备14008679号