180 likes | 414 Views
算法设计与分析 第三章 动态规划 - 背包问题. 杨圣洪. 最化算法: 动态规划 基本步骤. 找出 最优解 的性质,并刻划其 结构特征。 递归定义最优值。有难度。 以 自底向上 的方式计算出 最优值 。 非递归 根据计算最优值时得到的信息, 构造 最优解。! 第 (1) 为 整体 最优是否有 局部 最优。. 3.10 0-1 背包问题. 给定 n 种 物品 和一 背包 。物品 i 的 体积 是 wi ,其价值为 vi ,背包的 容量 为 C 。问应如何选择装入 背包 的 物品 ,使得装入背包中物品的 总价值 最大 ? 0-1 背包 问题是一个
E N D
最化算法:动态规划基本步骤 • 找出最优解的性质,并刻划其结构特征。 • 递归定义最优值。有难度。 • 以自底向上的方式计算出最优值。非递归 • 根据计算最优值时得到的信息,构造最优解。! 第(1)为整体最优是否有局部最优。
3.10 0-1背包问题 • 给定n种物品和一背包。物品i的体积是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大? • 0-1背包问题是一个 • 特殊的整数规划问题。 • (板右边表达式)
3.10 0-1背包问题--最优子结构 • 给定n种物品和一背包。物品i的体积是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 若(y1,y2,…,yn)是全局最优解,则(y2,y3,…,yn)是子问题的最优解。 • 反证法假设子问题的最优解不是(y2,y3,…,yn),而是(z2,…,zn) 。 • 则z2v2+z3v3+…+znvn>y2v2+ y3v3 +…+ynvn, • 且z2w2 +…+znwnc-w1y1。 • 故w1y1+z2w2 +…+znwnc即(y1,z2,z3,…,zn)是原问题的可行解 • 但y1v1+ z2v2+z3v3+…+znvn> y1v1+ y2v2+ y3v3 +…+ynvn • 即(y1,z2,z3,…,zn)的价值高于(y1,y2,…,yn),这与(y1,y2,…,yn)是最优解矛盾!故假设错。
递归公式 • 将子问题最优值记为m(i,j),其中j为背包容量,i为候选物品的起始号,即候选物品为i,i+1,…,n。递归式如下: • 其中: (板递归公式) • m(i+1,j) 为不放物品i的最大价值 • m(i+1,j-wi)+vi 为放物品i.的最大值 • m(n,j) 从物品n开始逆序试装,直到m(1,C)
将子问题最优值记为m(i,j),j背包容量,i为候选物品的起始号。递归式如下: (活动区给出该公式,多算关键交互)
算法 void Knapsack(real* v,int* w,int c,int n,real** m){ //初始化数组m(n,j) for(int j=0;j<w[n] && j<=c;j++){m[n][j]=0;} for(int j=w[n];j<=c;j++){m[n][j]=v[n];} //已经最后一行m[n][j]的值,倒过计算m[i][j]的值 for (int i=n-1;i>1;i--){ //当容量j<w[i]时,物品i放不进,直接取同容量下行值 for(int j=0;j<w[i] &&j<=c;j++){m[i][j]=m[i+1][j];} //为j=w[i],+1,+2,..,c时价值 for(int j=w[i];j<=c;j++) { m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);} } //以下i=1 if (c<w[1]) { //首行只要直接考虑容量j=c的情形,j<c均不考虑m[1][c]=m[2][c];} else { //如果包容量c>w[1]即物1能放入包中 m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);}} //边讲边板算法 问题: 体积w[i] 不是整数 时如何办? 价值呢?
构造 当m(i,j)m(i+1,j)时,表示 m(i,j)=m(i+1,j-wi)+vi,表示 物品i被选中即xi=1,最后一列 下一个价值是m(i+1,j-wi) void Knapsack(real* v,int* w,int c,int n,real** m){ for(int j=0;j<w[n] && j<=c;j++){ m[n][j]=0;} for(int j=w[n];j<=c;j++){m[n][j]=v[n];} for (int i=n-1;i>1;i--){ for(int j=0;j<w[i] &&j<=c;j++){ m[i][j]=m[i+1][j];} for(int j=w[i];j<=c;j++) { m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i]);} } //以下i=1 if (c<w[1]) { m[1][c]=m[2][c];} else { m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]); }} m[1][10]m[2][10] x[1]=1 j-wi=10-2=8 v=6 m[2][8]m[3][8] x[2]=1 j-wi=8-2=6,v=6+3=9 m[3][6]=m[4][6] x[3]=0 j=6 m[4][6]=m[5][6] x[4]=0 j=6 m[n][c]=m[5][6]>0 x[5]=1 j-wi=6-4=2,v=9+6
构造最优解算法描述 当m(i,j)m(i+1,j)时,表示 m(i,j)=m(i+1,j-wi)+vi,表示 物品i被选中即xi=1,最后一列 下一个价值是m(i+1,j-wi) void traceback(int**m,int* w,int c,int n,int *x){ for (int i=1;i<n;i++) { if (m[i][c]==m[i+1][c]) {x[i]=0;} else {x[i]=1;c=c-w[i];} } x[n]=(m[n][c]>0)?1:0; } m[1][10]m[2][10] x[1]=1剩余容量c=10-2=8 v=6 m[2][8]m[3][8] x[2]=1 剩余容量c=8-2=6,v=6+3=9 m[3][6]=m[4][6] x[3]=0 剩余容量c=6 m[4][6]=m[5][6] x[4]=0 剩余容量c=6 m[n][c]=m[5][6]>0 x[5]=1 剩余容量c=6-4=2,v=9+6
计算最优值算法及其复杂性 void Knapsack(real* v,int* w,int c,int n,real** m){ for(int j=0;j<w[n] && j<=c;j++) {m[n][j]=0;} for(int j=w[n];j<=c;j++){m[n][j]=v[n];} for (int i=n-1;i>1;i--){ for(int j=0;j<w[i] &&j<=c;j++){ m[i][j]=m[i+1][j];} for(int j=w[i];j<=c;j++) { m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i]);} } //以下i=1 if (c<w[1]) { m[1][c]=m[2][c];} else { m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]); }} 数组m[n][c] 从m(i,j)的递归式及左边程序可知:计算时间为O(nc)。 当c很大时,计算时间较多。 例如c>2n时,需要Ω(n2n)。 能否优化呢?
关于m(i,j) • m(i,j)中很多值是重复的,重复计算浪费时间,只计算不重复的数据则节约时间 • 用p[i]记录第i的非重复值。点数据结构为(容量j,价值m(i,j)) • p[5]={(0,0),(4,6)} • p[4]={(0,0),(4,6),(9,10), (10,10) } • p[3]={(0,0),(4,6),(9,10),(10,11)} • p[2]={(0,0),(2,3),(4,6),(6,9),(9,10),(10,11)} • p[1]={(0,0),(2,6),(4,9),(6,12),(8,15)} • 如何递归算出p[i]呢?
快捷算法 • 由m(i,j)的计算公式可知,m(i,j)跳跃点集p[i] = • m(i+1,j)的跳跃点集p[i+1] m(i+1,j-wi)+vi的跳跃点集q[i+1]。 • q[i+1]=p[i+1]各点加上(wi,vi) • 如何体现max(m(i+1,j), m(i+1,j-wi)+vi ) 对于p[i+1]q[i+1]中的2个点(a,b)和(c,d),若点(c,d)的ca且d<b即(c,d)体积大价值低,则应剔除。 • q[i+1]=p[i+1]各点加上(wi,vi), (板) • p[i] =p[i+1] (q[i+1]-受控点) (板) • p[n+1]={(0,0)}, (板)
快捷算法(重点) • p[n+1]={(0,0)}, q[n+1] =p[n+1]+(wn,vn) p[n]=p[n+1]q[n+1]-低价点 q[n]=p[n]+(wn-1,vn-1)-超容点 p[n-1]=p[n]q[n]-低价点 …… q[i+1]=p[i+1]+(wi,vi)-超容点 p[i]=p[i]q[i]-低价点 …… q[2]=p[2]+(w1,v1)-超容点 p[1]=p[2]q[2]-低价点 p[6]={(0,0)} q[6]=p[6]+(4,6)={(4,6)} P[5]=p[6]+q[6]={(0,0),(4,6)} Q[5]=p[5]+(5,4)={(5,4),(9,10)} P[4]=p[5]+q[5]={0,0),(4,6),(5,4),(9,10)}-(5,4) ={(0,0),(4,6),(9,10)} Q[4]=p[4]+(6,5) ={(6,5),(10,11),(15,15)} ={(6,5),(10,11)} P[3]=p[4]+q[4]-(6,5) ={(0,0),(4,6),(9,10),(10,11)} Q[3]=p[3]+(2,3)={(2,3),(6,9)} P[2]=p[3]+q[3] ={(0,0),(2,3),(4,6),(6,9),(9,10),(10,11)} Q[2]=p[2]+(2,6) ={(2,6),(4,9),(6,12),(8,15)} P[1]=p[2]+q[2] ={(0,0),(2,6),(4,9),(6,12),(8,15)}
算法设计与实现 p[6]={(0,0)} , q[6]={(4,6)} p[5]={(0,0),(4,6)}, q[5]={(5,4),(9,10)} p[4]={(0,0),(4,6),(9,10)}, q[4]={(6,5),(10,11)} p[3]={(0,0),(4,6),(9,10),(10,11)}, q[3]={(2,3),(6,9)} p[2]={(0,0), (2,3),(4,6) , (6,9),(9,10),(10,11)} q[2]={(2,6),(4,9),(6,12),(8,15)} p[1]={(0,0),(2,6),(4,9),(6,12),(8,15)} P[i],q[i]的元素个数<=c p={ (0,0) , (0,0),(4,6), (0,0),(4,6),(9,10), (0,0),(4,6),(9,10),(10,11), (0,0), (2,3),(4,6) , (6,9),(9,10),(10,11) , (0,0),(2,6),(4,9),(6,12),(8,15)} q={ (4,6) , (5,4),(9,10), (6,5),(10,11), (2,3),(6,9) (2,6), (4,9),(6,12) , (8,15)} (难点) 各保存到一个数组中<=nc
p={ (0,0) , (0,0),(4,6), (0,0),(4,6),(9,10), (0,0),(4,6),(9,10),(10,11), (0,0), (2,3),(4,6) , (6,9),(9,10),(10,11) , (0,0),(2,6),(4,9),(6,12),(8,15)} q={ (4,6) , (5,4),(9,10), (6,5),(10,11), (2,3),(6,9) (2,6), (4,9),(6,12) , (8,15)} 各保存到一个数组中<=nc p=new Array(n*c+1);//每个元素对应可能有c个不同的值,p[n+1]={(0,0)} (板) q=new Array(n*c+1);//值应该能保存 (板) hp=new Array(n+1);//物品i在p数组中的指针 (板) hq=new Array(n+1);//物品i在q数组中的指针 (板) for (i=0;i<n*c+1;i++){ p[i]=new Array(2);//每行二个元素,如(0,0) q[i]=new Array(2);//每行二个元素,如(5,4) } knapscak2(w,v,n,c);//体积数组,价值数组,物品个数,袋子的总体积
q[i+1]=p[i+1](wi,vi), p[i]=p[i+1]q[i+1]-受控跳 //转抄p[i+1]到p[i] for (j=kp;j<=ip0;j++){ip=ip+1; p[ip][0]=p[j][0]; p[ip][1]=p[j][1];} //转抄q[i+1]-受控跳 for (j=iq0+1;j<=iq;j++){ t0=q[j][0];t1=q[j][1]; for (k=ip0;k<=ip;k++){ //受控点不进入 if ((t0>=p[k][0]) && (t1<p[k][1])) {break;} //替换原来差劲点 else if ( (p[k][0]>=t0) && (p[k][1]<t1) && (p[k][0]>0)) {p[k][0]=t0;p[k][1]=t1;break;}} if (k>ip){ //非受控则进入 ip=ip+1;p[ip][0]=t0;p[ip][1]=t1;} } //转抄q[i+1]-受控跳结束 hp[i]=ip; } //物品i处理完 } void knapscak2(w,v,n,c){ p[0][0]=0; p[0][1]=0; q[0][0]=0; q[0][1]=0; hp[n]=0; hq[n]=0; ip=0; iq=0; var ip0=1; var iq0=0; var kp=0; var kq=0; var t0=0; var t1=0; var i=0; var j=0; var k=0; for(i=n-1;i>=0;i--) { //q[i+1]=p[i+1]+(wi,vi), if (i==n-1) { kp=0;} else{kp=hp[i+2]+1;} iq0=iq; //kp上个起始位=上上尾+1 for (j=kp;j<=hp[i+1];j++){ t0=p[j][0]+w[i];t1=p[j][1]+v[i]; if (t0<=c) { iq=iq+1;q[iq][0]=t0;q[iq][1]=t1;} } hq[i]=iq; ip0=ip;//当前值
q[i+1]={(j+wi,m(i,j)+vi}构造解 var imax=hq[1]+1//倒数第2轮的结束处+1为最后一轮起始点 for (i=hq[1]+1;i<=hq[0];i++) {imax=(q[i][1]>q[imax][1])?i:imax;} //以此最大值为基准不断倒查, var x=new Array(n); for (i=0;i<n;i++) {x[i]=0;} var t0=q[imax][0]; //8 var t1=q[imax][1]; //15 var mf=0; for(i=0;i<n;i++){ mf=0; for(j=n-1;j>=i;j--){ for (k=hq[j+1]+1;k<=hq[j];k++) { if ( (q[k][0]==t0) && (q[k][1]==t1) ) { x[j]=1;t0=t0-w[j]; t1=t1-v[j];mf=1;break; }} if (mf==1){ break; } }} q={ (4,6), (5,4),(9,10), (6,5),(10,11), (2,3),(6,9) (2,6), (4,9),(6,12) , (8,15)}
算法复杂度分析 • 算法描述:P80-81 • 主要计算量在于计算跳跃点集p[i](1≤i≤n)。 • 由于q[i+1]=p[i+1](wi,vi),算q[i+1]要O(|p[i+1]|)。 • p[i]=p[i+1]q[i+1]-受控跳跃点,O(|p[i+1]|)计算时间。 • 从p[i]的定义可以看出,p[i]中的跳跃点相应于xi,…,xn的0/1赋值,其实真值表可能组合。因此p[i]中跳跃点个数不超过2n-i+1,等比数例的和(qn-q0)/(q-1)=O(qn)。 • 由此可见,算法计算跳跃点集p[i]所花费的计算时间为 • 改进后算法的计算时间复杂性为O(2n)。 • 当所给物品的重量wi(1≤i≤n)是整数时,|p[i]|≤c+1,(1≤i≤n)。改进后计算时间复杂性为O(min{nc,2n})。