背包问题求解方法综述Word下载.docx
- 文档编号:21729674
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:30
- 大小:472.84KB
背包问题求解方法综述Word下载.docx
《背包问题求解方法综述Word下载.docx》由会员分享,可在线阅读,更多相关《背包问题求解方法综述Word下载.docx(30页珍藏版)》请在冰豆网上搜索。
约束条件:
,
2.0-1背包问题的求解算法
2.1蛮力算法(bruteforcemethod)
2.1.1基本思想:
对于有n种可选物品的0/1背包问题,其解空间由长度为n的0-1向量组成,可用子集数表示。
在搜索解空间树时,深度优先遍历,搜索每一个结点,无论是否可能产生最优解,都遍历至叶子结点,记录每次得到的装入总价值,然后记录遍历过的最大价值。
2.1.2代码实现:
#include<
iostream>
algorithm>
usingnamespacestd;
#defineN100//最多可能物体数
structgoods//物品结构体
{
intsign;
//物品序号
intw;
//物品重量
intp;
//物品价值
}a[N];
boolm(goodsa,goodsb)
return(a.p/a.w)>
(b.p/b.w);
}
intmax(inta,intb)
returna<
b?
b:
a;
intn,C,bestP=0,cp=0,cw=0;
intX[N],cx[N];
/*蛮力法求解0/1背包问题*/
intForce(inti)
if(i>
n-1){
if(bestP<
cp&
&
cw+a[i].w<
=C){
for(intk=0;
k<
n;
k++)X[k]=cx[k];
//存储最优路径
bestP=cp;
}
returnbestP;
cw=cw+a[i].w;
cp=cp+a[i].p;
cx[i]=1;
//装入背包
Force(i+1);
cw=cw-a[i].w;
cp=cp-a[i].p;
cx[i]=0;
//不装入背包
intKnapSack1(intn,goodsa[],intC,intx[])
Force(0);
intmain()
goodsb[N];
printf("
物品种数n:
"
);
scanf("
%d"
&
n);
//输入物品种数
背包容量C:
C);
//输入背包容量
for(inti=0;
i<
i++)//输入物品i的重量w及其价值v
{
物品%d的重量w[%d]及其价值v[%d]:
i+1,i+1,i+1);
%d%d"
a[i].w,&
a[i].p);
b[i]=a[i];
intsum1=KnapSack1(n,a,C,X);
//调用蛮力法求0/1背包问题
蛮力法求解0/1背包问题:
\nX=["
for(i=0;
i++)
cout<
<
X[i]<
"
;
//输出所求X[n]矩阵
]装入总价值%d\n"
sum1);
bestP=0,cp=0,cw=0;
//恢复初始化
2.1.3复杂度分析:
蛮力法求解0/1背包问题的时间复杂度为:
2^n
2.2贪心算法(Greedyalgorithm)
贪心算法通过一系列的选择来得到问题的解。
贪心选择即它总是做出当前最好的选择[4]。
贪心选择性质指所求问题的整体最优解可以通过一系列局部最优选择,这是贪心算法与动态规划算法的主要区别。
贪心算法每次只考虑一步,每一步数据的选取都必须满足局部最优条件。
在枚举剩下数据与当前已经选取的数据组合获得的解中,提取其中能获得最优解的唯一的一个数据,加入结果数据中,直到剩下的数据不能再加入为止[6]。
贪心算法不能保证得到的最后解是最佳的,也不能用来求最大或最小解问题,只能求满足某些约束条件的可行解X围。
2.2.1算法设计
用贪心算法解决0-1背包问题一般有以下三种策略:
价值最大者优先:
在剩余物品中,选出可以装入背包的价值最大的物品,若背包有足够的容量,以此策略,然后是下一个价值最大的物品。
但这种策略背包的承重量不能够得到有效利用,即得不到最优解。
例如:
n=3,w=[50,20,20],v=[10,7,7]c=55,得到的解是x=[1,0,0],这种方案的总价值为10,而最优解为[0,1,1],总价值为14。
重量最小者优先:
在剩余物品中,选择可以装入背包的重量最小的物品。
但这种策略,不能保证重量小的是最有价值的,也不能得到最优解。
n=2,w=[10,20],v=[5,100],c=25,得到的解为x=[1,0],而最优解是[0,1]。
单位价值最大者优先:
根据价值与重量的比值
/
,即单位价值,在剩下的物品中依次选取比值最大的物品装入背包。
这种策略也不能得到最优解。
n=3,w=[20,15,15],v=[40,25,25],
=[2,5/3,5/3],c=30,得到的解x=[1,0,0],而最优解是[0,1,1]。
但它是直觉上的一个近似解。
本文讨论该策略。
策略3的具体步骤为:
第一步:
计算每个物品的价值比
=
,i=1,2,…,n。
第二步:
对物品的价值比非递增排序。
第三步:
重复下面操作,直到有序列表中留下物品。
如果列表中的当前物品能够装入背包,就将它放入背包中,否则,处理下一个物品。
2.2.2编程实现
#include"
stdafx.h"
#include<
time.h>
Windows.h>
usingnamespacestd;
#definemax100//自定义物品最大数
voidpackage(intv[],intw[],intn,intc)//定义包函数
doublea[max];
inti,totalv=0,totalw=0,index[max];
a[i]=(double)v[i]/w[i];
//单位价值计算
index[i]=i;
for(i=1;
for(intj=0;
j<
n-i;
j++)
if(a[j]<
a[j+1])//进行循环判断
doubleb=a[j];
a[j]=a[j+1];
a[j+1]=b;
intc=v[j];
v[j]=v[j+1];
v[j+1]=c;
intd=w[j];
w[j]=w[j+1];
w[j+1]=d;
inte=index[j];
index[j]=index[j+1];
index[j+1]=e;
单位价值:
//输出单位价值
a[i]<
endl<
物品价值:
//输出物品价值
v[i]<
\t"
物品重量:
//输出物品重量
w[i]<
endl;
doublex[max]={0};
i=0;
while(w[i]<
=c)
x[i]=1;
c=c-w[i];
i++;
cout<
所选择的商品如下:
序号i:
\t重量w:
\t价格v:
//输出所选择的商品
if(x[i]==1){
totalw=totalw+w[i];
totalv=totalv+v[i];
index[i]+1<
背包的总重量为:
totalw<
//背包所装载总重量
背包的总价值为:
totalv<
//背包的总价值
intmain(void)//主函数定义
LARGE_INTEGERbegin,end,frequency;
QueryPerformanceFrequency(&
frequency);
srand(time(0));
intn,i,x[max];
intv[max],w[max],W;
//变量的定义
请输入物品种数n和背包容量W:
cin>
>
n>
W;
for(i=0;
x[i]=0;
//物品选择情况表初始化为0
v[i]=rand()%1000;
w[i]=rand()%1000;
商品的重量和价值如下:
for(inti=0;
{cout<
QueryPerformanceCounter(&
begin);
package(v,w,n,W);
//函数的调用
end);
时间:
<
(double)(end.QuadPart-begin.QuadPart)/frequency.QuadPart
s"
2.2.2运行结果
贪心算法求解0/1背包问题的时间复杂度为:
(nlogn)
2.3动态规划算法(DynamicProgramming)
20世纪50年代,美国数学家R.E.Bellman等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,把多阶段过程转化为一系列单阶段问题,利用个阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划[3]。
动态规划是基于递归的,通常不是一个解决KP有效的方式,因为空间消耗非常大,和最坏的和最好的计算工作通常是相同的[7]。
动态规划算法与分治法类似,其基本思想也是将带求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是互相独立的。
若用分治法解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间。
然而,不同子问题的数目常常只有多项式量级。
在用分治法求解时,有些子问题被重复计算了许多次。
如果我们能够保证已解决的子问题的答案,而在需要时找出已求出的答案,这样就可以避免大量的重复计算,从而达到多项式时间算法。
为了达到此目的可以用一个表来记录所有已解决的子问题的答案。
不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
这就是动态规划法的基本思想。
具体的动态规划算法多种多样,但它们有相同的填表格式。
动态规划算法适用于解最优化问题。
通常可按以下4个步骤设计:
(1)找出最优解的性质,并刻画其结构特征。
(2)递归地定义最优值。
(3)以自底向上的方式计算出最优值。
(4)根据计算最优值时得到的信息,构造最优解。
步骤
(1)~(3)是动态规划算法的基本步奏。
在需要求出最优值的情形,步骤(4)可以省去。
若需要求出问题的最优解,则必须执行步骤(4).此时,在步骤(3)中计算最优解时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速构造出一个最优解。
使用动态规划求解问题,最重要的就是确定动态规划3要素:
(1)问题的阶段;
(2)每个阶段的状态;
(3)从前一个阶段转化后一个阶段之间的递推关系[4]。
2.3.1分析最优解的性质,刻画最优解的结构特征——最优子结构性质分析
设
所给0-1背包问题的一个最优解,则
是下面相应子问题的一个最优解:
目标函数:
约束条件:
证明:
若
不是上述子问题的一个最优解,而
是他的最优解。
由此可知,
且
。
因此
这说明
是原问题的一个更优解,从而
不是所给原问题的最优解,产生矛盾。
所以
是上述子问题的一个最优解。
2.3.2递归关系
由于0-1背包问题的解是用向量
来描述的。
因此,该问题可以看做是决策一个n元0-1向量
对于任意一个分量
的决策是“决定
=1或
=0,i=1,2,…,n。
对
决策后,序列
已被确定,在决策
时,问题处于下列两个状态之一:
(1)背包容量不足以装下物品i,则=0,装入背包的价值不增加;
(2)背包容量足以装入物品i,则=1,装入背包的价值增加
在这种情况下,装入背包的价值最大化应该是对决策后的价值。
设所给0-1背包问题的子问题
的最优值为m(i,j),即m(i,j)是背包容量为j,可选择的物品为i,i+1,…,n时0-1背包问题的最优值。
由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式为:
2.3.3算法设计
基于上面的讨论,求解0-1背包的动态规划算法步骤如下:
步骤1:
当
为正整数时,用数组w[n]来存放n个物品的重量;
数组v[n]来存放n个物品的价值,背包容量为c,数组M[n+1][c+1]来存放每一次迭代的执行结果;
数组x[n]用来存储所装入背包的物品状态;
步骤2:
初始化。
数组M的第0行第0列全部设置为0;
步骤3:
循环阶段。
按式(5)确定前i个物品能够装入背包的情况下得到的最优值;
步骤3-1:
i=1时,求出M[1][j],1≤j≤c;
步骤3-2:
i=2时,求出M[2][j],1≤j≤c;
……
步骤3-n:
i=n时,求出M[n][c]。
此时,M[n][c]便是最优值;
步骤4:
确定装入背包的具体物品。
从M[n][c]的值向前推,如果M[n][c]>
M[n-1][c],表明第n个物品被装入背包,则
=1,前n-1个物品没有被装入背包,则
=0,前n-1个物品被装入容量为c的背包中。
以此类推,知道确定第1个物品是否被装入背包为止。
由此,得到下面的关系式:
如果M[i][j]=M[i-1][j],说明第i个物品没有被装入背包,则
=0;
如果M[i][j]>
M[i-1][j],说明第i个物品被装入背包,则
=1,j=j-
按照上述关系式,从M[n][c]的值向前倒推,即j初始为c,i初始为n,即可确定装入背包的具体物品。
上述算法需要O(nc)时间计算时间。
不过上述算法有2个明显的确点。
一是算法要求所给物品的重量
(1≤i≤n)是整数;
二是当背包容量c很大时,算法需要的计算时间较多。
O(nm)
2.4回溯法(Backtracking)
2.4.1回溯法0-1背包问题的实现
回溯法是一种系统地搜索问题解答的方法。
为了实现回溯,首先需要为问题定义一个解空间,这个解空间必须至少包含问题的一个解(可能是最优的)。
一旦定义了解空间的组织方要选择一个对象的子集,将它们装人背包,以便获得的收益最大,则解空间应组织成子集树的形状。
首先形成一个递归算法,去找到可获得的最大收益。
然后,对该算法加以改进,形成代码。
改进后的代码可找到获得最大收益时包含在背包中的对象的集合。
左子树表示一个可行的结点,无论何时都要移动到它,当右子树可能含有比当前最优解还优的解时,移动到它。
一种决定是否要移动到右子树的简单方法是r为还未遍历的对象的收益之和,将r加到cp(当前节点所获收益)之上,若(r+cp)<
=bestp(目前最优解的收益),则不需搜索右子树。
一种更有效的方法是按收益密度vi/wi对剩余对象排序,将对象按密度递减的顺序去填充背包的剩余容量。
2.4.2编程实现如下
#defineN100//最多可能物体数
structgoods//物品结构体
//物品价值
}a[N],b[N];
intmax1(inta,intb)//最大函数定义
intn,W,bestP=0,cp=0,cw=0;
//变量定义
intBackTrack(inti)
if(bestP<
cp){
for(intk=0;
bestP=cp;
}
returnbestP;
if(cw+a[i].w<
=W){//进入左子树
cw=cw+a[i].w;
cp=cp+a[i].p;
cx[a[i].sign]=1;
//装入背包
BackTrack(i+1);
cw=cw-a[i].w;
cp=cp-a[i].p;
//回溯,进入右子树
cx[a[i].sign]=0;
//不装入背包
BackTrack(i+1);
voidKnapSack3(intn,goodsa[],intC,intx[])
inttotalW=0;
for(inti=0;
x[i]=0;
a[i].sign=i;
sort(a,a+n,m);
//将各物品按单位重量价值降序排列
BackTrack(0);
i++)//进行循环输出
{
if(X[i]==1){
cout<
i+1<
totalW+=b[i].w;
b[i].w<
b[i].p<
放入背包的物品总重量为:
totalW;
放入背包的物品总价值为:
bestP<
intmain()//主函数的定义
cin>
for(inti=0;
i++)//输入物品i的重量w及其价值v
a[i].w=rand()%1000;
a[i].p=rand()%1000;
b[i]=a[i];
物品的重量和价值分别如下:
cout<
a[i].w;
a[i].p<
KnapSack3(n,a,W,X);
//调用回溯法求0/1背包问题
}
2.4.3运行结果
回溯法法的时间复杂度为
2.5分枝-限界法(Branch-thresholdmethod)
2.5.1分枝-限界法的基本原理与分析
分枝限界法是另一种系统地搜索解空间的方法,它与回溯法的主要区别在于对E-结点的扩充方式。
每个活结点有且仅有一次会变成E-结点。
当一个结点变为E-结点时,则生成从该结点移动一步即可
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 背包 问题 求解 方法 综述