用动态规划算法解决0-1背包问题
来源:互联网 发布:松井珠理奈 知乎 编辑:程序博客网 时间:2024/06/14 09:58
1.使用动态规划算法必须具备两个基本要素:最优子结构性质和重叠子问题性质
2.动态规划算法常以自底向上的方式计算最优值,也就是说,从最小的子问题开始向包含该子问题的大问题方向求解,把每一次求解出的子问题的解保存下来,以便提供给包含该小问题的大问题使用,因此使用循环迭代方式计算更为合理,但从动态规划算法的两个基本要素可以看出,直接以递归方式计算更为简便,于是乎,就产生了动态规划算法的变形方法:备忘录算法。备忘录算法在递归调用过程中将已经求出的子问题的解保存下来,以便下次递归调用遇到相同的子问题可直接获取该子问题的解。
3.动态规划算法在求解最优值的过程中,将遇到的所有子问题的解记录在一张表中,同时也将一些能构造最优解的必要信息记录下来,以便用这些信息构造最优解。求解最优值的过程也俗称“填表过程”。
4.设计动态规划算法的通常的步骤:
(1)找出最优解的性质,并刻画其结构特征
(2)递归地定义最优值
(3)以自底向上的方式计算最优值
(4)根据计算最优值时得到的信息构造最优解
5.0-1背包问题具有最优子结构性质和重叠子问题性质。
定义m(i,j):从物品i、i+1、...、n-1、n中选择物品装入容量大小为j的背包,使该背包的总价
值最大,最大值为m(i,j),也即最优值。
假设背包容量为c,物品总数n,物品i的重量表示wi,价值表示vi,于是0-1背包问题的最优值为
m(1,c)。计算m(1,c)的值之前,先递归地定义最优值的计算方法:
(1)当背包剩余容量为j时,且j >= wi, m(i,j) = max{ m(i+1,j), m(i+1,j-wi)+vi)};说明
此时的背包剩余容量大于物品i的重量,于是在选取物品i和舍弃物品i之中选择较大的。
(2)当背包剩余容量为j时,且0< j < wi, m(i,j) = m(i+1,j); 说明物品i的重量大于背包此时
剩余容量,于是,对物品i舍弃。
(3)最小的子问题,也即只有一个物品时,如何装入背包使背包的总价值最大。假设背包剩余
容量为j时,同时只有一个物品n供选择时,此时,若j > wn, 则最大价值为wn,即 m(n,j) =
wn;若j < wn, 则最大价值为0,即 m(n,j) = 0。
注:上述考虑的物品重量和背包容量都为整数。因此,使用二维数组m[i][j]表示m(i,j)
以下是源代码:
#include "stdafx.h";#include <iostream>;using namespace std;#define C 10#define N 5void Knapsack(int *w, int *v, int n, int c, int (*m)[C+1]){ //先处理记录表格中的最后一行,也即完成对m[n][j]的“填空” int lowc = w[n]-1 > c ? c : w[n]-1; for (int j = 0; j <= lowc; j++) m[n][j] = 0; for (int j = lowc+1; j <= c; j++) m[n][j] = v[n]; //循环处理记录表格中的其他行,也即完成对m[i][j]的“填空” for (int i = n-1; i > 1; i--) { lowc = w[i]-1 > c ? c : w[i]-1; for (int j = 0; j <=lowc; j++) m[i][j] = m[i+1][j]; for (int j = lowc+1; j <=c; j++) { int t1 = m[i+1][j]; int t2 = m[i+1][j - w[i]] + v[i]; m[i][j] = t1 > t2 ? t1 : t2; } } //处理记录表格中的第一行,也即完成对m[1][c]的“填空” m[1][c] = m[2][c]; //背包容量c不足,物品1舍去 if(c >= w[1] && m[2][c-w[1]] + v[1] > m[2][c]) //背包容量c足够,并且选取物品1的情况价值较大 m[1][c] = m[2][c-w[1]] +v[1];}void Traceback(int (*m)[C+1],int *w, int *x,int n, int c){ for (int i = 1; i < n; i++) { if(m[i][c] == m[i+1][c]) x[i] = 0; else { x[i] = 1; c -= w[i]; } } w[n] < c ? x[n] =1 : x[n] =0;}int _tmain(){ int x[N+1]; int w[N+1] ={-1,2,2,6,5,4}; int v[N+1] ={-1,6,3,5,4,6}; int m[N+1][C+1]; Knapsack(w,v,N,C,m); cout<<m[1][C]<<endl; Traceback(m, w, x, N, C); for (int i = 1; i <= N; i++) { cout<<x[i]<<" "; } cout<<endl; system("pause"); return 0;}
运行结果如下:
15
1 1 0 0 1
从上述算法分析中,有两个较明显的缺点。其一是算法要求所给物品的重量wi是整数。其次,背包容量c很大时,算法所需要的计算时间和空间较多。因此,对上述算法提出改进,使物品重量和背包容量允许为实数(大于0的实数),同时减少所需的时间和空间复杂度。改进分析:
1.将二维表m(i,j)看成函数,对于确定的i,函数m(i,j)是关于变量j的阶梯状单调不减函数。通过跳跃点描述阶梯状的函数m(i,j)。比如,物品n重量大小5,价值为8那么,
当j>=5时,m(n,j) = 8 ;当0<= j < 5时, m(n,j) = 0;可以看出背包容量大小5是m(n,j)的分界容量,所以,(0,0)和(5,8)是函数m(n,j)的两个跳跃点。因此,只需对函数m(i,j)所有的跳跃点保存,无需建立二维表m,从而节省内存空间。最后的最优值从函数m(1,j)的所有跳跃点中获得。
2.每个跳跃点对应一个数组,记录以往选取物品的编号。最优解的构造从函数m(1,j)中的第一个不超过背包容量c对应的跳跃点中获得。(注意,对于确定的i,函数m(i,j)所有的跳跃点按j递增排序。)
3.用p[i+1]表示函数m(i+1,j)的跳跃点集,q[i+1]表示p[i+1]包含物品i之后的跳跃点集,则p[i]等于p[i+1]与q[i+1]的合并(并不是单纯合并,因为有些跳跃点受控于其他的跳跃点)。
举例说明:
背包容量c:5
物品数量n:3
物品重量数组w:2,3,5
物品价值数组v:6,4,8
初始化p[4]的跳跃点集:(0,0)
若加入物品3之后,出现的跳跃点:(5,8),所以合并之后,
p[3]的跳跃点集:(0,0),(5,8)
若加入物品2之后,出现的跳跃点:(3,4),(8,12),所以合并之后,
p[2]的跳跃点集:(0,0),(3,4),(5,8),(8,12)
若加入物品1之后,出现的跳跃点:(2,6),(5,10),(7,14),(10,18),所以合并之后,
p[1]的跳跃点集:(0,0),(2,6),(5,10),(7,14),(10,18)
最终的最优值从p[1]的跳跃点集中寻找,比如:
(1)若背包容量5,对应的跳跃点为(5,10),因此,背包装载物品的最大价值为10.
(2)若背包容量8,对应的跳跃点为(7,14),因此,背包装载物品的最大价值为14.
以下是改进算法:
#include "stdafx.h"#include <iostream>using namespace std;//物品类class Object{ friend class Knapsack; int number; double weight; double value;};//跳跃点类class JumpNode{ friend class Knapsack; friend void Print(); double enoughw; //排序的依据 double enoughv; int amount; int *selected; JumpNode *next; };//0-1背包问题主类class Knapsack{ Knapsack(int n, double *w, double *v, double c); friend void Print();public: void DynamicProgram();private: int n; double c; Object *Ot; JumpNode *firstp; JumpNode *firstq; JumpNode *abandon;};//动态规划解决0-1背包问题(物品重量允许大于零的实数)void Knapsack::DynamicProgram(){ int i = n; for (; i >= 1; i--) { //对firstp中的结点用物品i进行“升级”,保存在firstq中 JumpNode *jptr = firstp; JumpNode *lastq = firstq; bool isfq = true; while (jptr != nullptr) { if(jptr->enoughw + Ot[i].weight <= c) { JumpNode *temp; if(abandon != nullptr) { temp = abandon; abandon = temp->next; } else { temp = new JumpNode; temp->selected = new int[n+1]; } temp->amount = jptr->amount + 1; temp->enoughw = jptr->enoughw + Ot[i].weight; temp->enoughv = jptr->enoughv + Ot[i].value; for (int j = 1; j < temp->amount ; j++) temp->selected[j] = jptr->selected[j]; temp->selected[temp->amount] = Ot[i].number; temp->next = nullptr; //把temp结点插入到firstq中 if(isfq) { firstq = temp; lastq = temp; isfq = false; } else { lastq->next = temp; lastq = temp; } } jptr = jptr->next; } if(!isfq) lastq->next = nullptr; //对firstp和firstq合并,最后放到firstp if(firstq != nullptr) { JumpNode *firsttemp = nullptr; JumpNode *temp = nullptr; bool isfirst = true; double cmaxvalue = 0; while ((firstp != nullptr) && (firstq != nullptr)) { if(firstp->enoughw < firstq->enoughw) { if(isfirst) { temp = firstp; firstp = firstp->next; firsttemp = temp; isfirst = false; cmaxvalue = temp->enoughv; } else { temp->next = firstp; temp = firstp; firstp = firstp->next; cmaxvalue = temp->enoughv; } } else { if(firstp->enoughw == firstq->enoughw) { if(firstp->enoughv > firstq->enoughv) { temp->next = firstp; temp = firstp; firstp = firstp->next; cmaxvalue = temp->enoughv; JumpNode *ab; ab = firstq->next; if(abandon != nullptr) //回收 { firstq->next = abandon->next; abandon->next = firstq; } else abandon = firstq; firstq = ab; } else { temp->next = firstq; temp = firstq; firstq = firstq->next; cmaxvalue = temp->enoughv; JumpNode *ab; ab = firstp->next; if(abandon != nullptr) //回收 { firstp->next = abandon->next; abandon->next = firstp; } else abandon = firstp; firstp = ab; } } else //firstp->enoughw > firstq->enoughw { if(firstq->enoughv > cmaxvalue) { temp->next = firstq; temp = firstq; firstq = firstq->next; cmaxvalue = temp->enoughv; } else { JumpNode *ab; ab = firstq->next; if(abandon != nullptr) //回收 { firstq->next = abandon->next; abandon->next = firstq; } else abandon = firstq; firstq = ab; } } } }//while if(firstq == nullptr) while (firstp != nullptr) { if(firstp->enoughv <= cmaxvalue) //回收 { JumpNode *ab; ab = firstp->next; if(abandon != nullptr) //回收 { firstp->next = abandon->next; abandon->next = firstp; } else abandon = firstp; firstp = ab; } else { temp->next = firstp; temp = firstp; firstp = firstp->next; cmaxvalue = temp->enoughv; } } else { while (firstq != nullptr) { if(firstq->enoughv <= cmaxvalue) //回收 { JumpNode *ab; ab = firstq->next; if(abandon != nullptr) //回收 { firstq->next = abandon->next; abandon->next = firstq; } else abandon = firstq; firstq = ab; } else { temp->next = firstq; temp = firstq; firstq = firstq->next; cmaxvalue = temp->enoughv; } } } temp->next = nullptr; firstp = firsttemp; } }//for}Knapsack::Knapsack(int n, double *w, double *v, double c):n(n),c(c){ Ot = new Object[n+1]; for (int i = 1; i <=n; i++) { Ot[i].number = i; Ot[i].weight = w[i-1]; Ot[i].value = v[i-1]; } //创建跳跃点(0,0) JumpNode *newNode = new JumpNode; newNode->amount = 0; newNode->enoughv =0; newNode->enoughw =0; newNode->next =nullptr; newNode->selected = new int[n+1]; firstp = newNode; firstq = nullptr;}void Print(){ const int N = 4; //物品数量 int c = 7; //背包容量 double w[N] = {2,3,5,2}; //物品重量数组 double v[N] = {6.6,4.6,8.5,4.6}; //物品价值数组 Knapsack *K = new Knapsack(N, w, v, c); K->DynamicProgram(); JumpNode *scan = K->firstp; int *selarr; int count = 0; double selvalue = 0; JumpNode *oldscan = scan; while (scan != nullptr) { if(scan->enoughw > c) break; oldscan = scan; scan = scan->next; } selarr = oldscan->selected; selvalue = oldscan->enoughv; count = oldscan->amount; if(count > 0) cout<<"已选的物品编号如下:"<<endl; //物品编号从1开始 for (int i = count; i >0; i--) cout<<selarr[i]<<" "; if(count == 0) cout<<"背包容量不足,任何物品都不能装入背包"<<endl; else cout<<endl<<"背包装载的物品最大价值:"<<selvalue<<endl; system("pause");}int _tmain(){ Print(); return 0;}
运行结果如下:
已选的物品编号如下:
1 2 4
背包装载的物品最大价值:15.8
- 用动态规划算法解决0-1背包问题
- 动态规划算法解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0/1背包问题
- 动态规划解决0/1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 动态规划解决0-1背包问题
- 用动态规划算法的变形方法——备忘录方法,解决0-1背包问题
- 0-1背包问题动态规划算法
- 动态规划算法-解决01背包问题
- mvc小结
- HDU 5675 ztr loves math——BestCoder Round #82(div.2)
- php创建锁机制
- HTML5 name属性
- io详解
- 用动态规划算法解决0-1背包问题
- 通过ExifInterface 读取图片信息
- 重读《大数据时代》:关于大数据的再认识
- Android四大基本组件介绍与生命周期
- nio详解
- jar包是什么?
- 一个服务器跑多个tomcat的设置
- HAL库-STM32F4 外部中断-延时
- 【模板】图的双联通分支 ,tarjan算法