0-1背包问题
一、 问题描述
1 0-1背包问题
现有n种物品,对1<=i<=n,已知第i种物品的重量为正整数Wi,价值为正整数Vi,背包能承受的最大载重量为正整数W,现要求找出这n种物品的一个子集,使得子集中物品的总重量不超过W且总价值尽量大。在选择装人背包的物品时,对每种物品i只有两种选择,即装入背包、不装入背包。不能将物品i 装人背包多次,也不能只装入部分的物品i(注意:这里对每种物品或者全取或者一点都不取,不允许只取一部分)。
背包问题的数学描述如下:
根据问题描述,可以将其转化为如下的约束条件和目标函数:
于是,问题就归结为寻找一个满足约束条件(1),并使目标函数式(2)达到最大的解向量 。
2 首先说明一下0-1背包问题拥有最优解
假设 是所给的问题的一个最优解,则是下面问题的一个最优解: 。如果不是的话,设 是这个问题的一个最优解,则,且。
因此, ,这说明 是所给的0-1背包问题比 更优的解,从而与假设矛盾。
二、算法设计
1 0-1背包问题的实现
用贪心算法求解0-1背包问题的步骤是,首先计算每种物品单位重量的价值vi/wi;然后,将物品的vi/wi的大小进行降序进行排列,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总量未超过c,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直进行下去,直到背包装满为止。
2 编程实现如下
#include"stdafx.h"
#include <iostream>
#include<time.h>
#include<Windows.h>
using namespacestd;
#define max 100//自定义物品最大数
void package(int v[],int w[],int n,int c)
{
doublea[max];
inti,totalv=0,totalw=0,index[max];
for(i=0;i<n;i++)
{
a[i]=(double)v[i]/w[i];
index[i]=i;
}
for(i=1;i<n;i++)
{
for(int j=0;j<n-i;j++)
{
if(a[j]<a[j+1])
{
double b=a[j];
a[j]=a[j+1];
a[j+1]=b;
int c=v[j];
v[j]=v[j+1];
v[j+1]=c;
int d=w[j];
w[j]=w[j+1];
w[j+1]=d;
int e=index[j];
index[j]=index[j+1];
index[j+1]=e;
}
}
}
cout<<"单位价值:";
for(i=0;i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl<<"物品价值:";
for(i=0;i<n;i++)
{
cout<<v[i]<<"\t";
}
cout<<endl<<"物品重量:";
for(i=0;i<n;i++)
{
cout<<w[i]<<"\t";
}
cout<<endl;
doublex[max]={0};
i=0;
while(w[i]<=c)
{
x[i]=1;
c=c-w[i];
i++;
}
cout<<"所选择的商品如下:"<<endl;
cout<<"序号i:\t重量w:\t价格v:\t"<<endl;
for(i=0;i<n;i++)
{
if(x[i]==1){
totalw=totalw+w[i];
totalv=totalv+v[i];
cout<<index[i]+1<<"\t"<<w[i]<<"\t"<<v[i]<<endl;
}
}
cout<<"背包的总重量为:"<<totalw<<endl;//背包所装载总重量
cout<<"背包的总价值为:"<<totalv<<endl;//背包的总价值
}
int main(void)
{
LARGE_INTEGER begin,end,frequency;
QueryPerformanceFrequency(&frequency);
srand(time(0));
intn,i,x[max];
int v[max],w[max],W;
cout<<"请输入物品种数n和背包容量W:";
cin>>n>>W;
for(i=0;i<n;i++)
x[i]=0; //物品选择情况表初始化为0
for(i=0;i<n;i++)
{
v[i]=rand()%1000;
w[i]=rand()%1000;}
cout<<"商品的重量和价值如下:"<<endl;
for(int i=0;i<n;i++)
{ cout<<w[i]<<"\t";
cout<<v[i]<<endl;
}
QueryPerformanceCounter(&begin);
package(v,w,n,W);
QueryPerformanceCounter(&end);
cout<<"时间:"
<<(double)(end.QuadPart- begin.QuadPart) / frequency.QuadPart
<<"s"<<endl;
}
3 运行结果及分析
运行结果如上图所示。设置的物品数量是10件,背包的容量是1000。由运行结果可知,首先求出了物品的单位重量的价值,并对其进行了降序排序,所以贪心算法会选择vi/wi值最大的序号为6的物品,因为其重量为145小于背包的容量,于是继续选择vi/wi值第二大的5号物品装入背包中,此时背包已装物品重量为609,由于vi/wi值第三大的物品质量为491,如果将其装入背包则背包重量大于容量1000,故此时贪心算法完成,背包总价值为1667。
2 动态规划算法
2.1 动态规划的基本原理与分析
动态规划算法的基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。但是经分解得到的子问题往往不是互相独立的。不同子问题的数目常常只有多项式量级。如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。它把已知问题分为很多子问题,按顺序求解子问题,在每一种情况下,列出各种情况的局部解,按条件从中选取那些最有可能产生最佳的结果舍弃其余。前一子问题为后面子问题提供信息,而减少计算量,最后一个子问题的解即为问题解。采用此方法求解0-1背包问题的主要步骤如下:
①分析最优解的结构:最有子结构性质;
②建立递归方程;
③计算最优值;
④构造最优解。
2.2 0-1背包问题的实现
① 最优子结构性质
0-1背包问题具有最优子结构性质。设(y1,y2…yn)是所给0-1背包问题的一个最优解,则(y2,y3…yn)是下面相应子问题的一个最优解:
因若不然,设(z2,z3…zn)是上述问题的一个最优解,而(y2,y3…yn)不是它的最优解,由此可见 ,且。因此
这说明(y1,z2…zn)是所给0-1背包问题的一个更优解,从而(y1,y2…yn)不是所给0-1背包问题的最优解。此为矛盾。
② 递归关系
设所给0-1背包问题的子问题
的最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,……,n时0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下:
2.3 编程实现如下
#include "stdafx.h"
#include<time.h>
#include<Windows.h>
#include<iostream>
#include<iomanip>
using namespacestd;
const intMAX=1000;
int w[MAX],v[MAX],best[MAX];
int V[MAX][MAX]; //最大价值矩阵
int W,n; //W为背包的最大载重量,n为物品的数量
//求最大值函数
int max1(intx,int y)
{
return x>= y?x:y;
}
//求最小值函数
int min1(intx,int y)
{
returnx>= y ? y:x;
}
void Knaspack()
{
intMax=min(w[n]-1,W);
for(int j=1; j <= Max ; j++)
V[n][j]=0;
for( int j=w[n]; j<= W ; j++)
V[n][j]=v[n];
for(int i=n-1;i > 1 ; i--)
{
Max=min(w[i]-1,W);
for(int j=1; j <= Max ; j++)
V[i][j]=V[i+1][j];
for(int j=w[i]; j <= W; j++)
V[i][j]=max(V[i+1][j],V[i+1][j-w[i]]+v[i]);
}
V[1][W]=V[2][W]; //先假设第一个物品不放入
if(W >w[1])
V[1][W]=max(V[1][W],V[2][W-w[1]]+v[1]);
}
//生成向量数组,决定某一个物品是否应该放入背包
void Traceback()
{
for(int i=1; i < n ; i++) //比较矩阵两邻两行(除最后一行),背包容量为W的最优值.
{
if(V[i][W]== V[i+1][W]) //如果当前行的最优值与下一行的最优值相等,则表明该物品不能放入。
best[i]=0;
else //否则可以放入
{
best[i]=1;
W-=w[i];
}
}
best[n]=(V[n][W] )?1:0;
}
void main()
{
LARGE_INTEGER begin,end,frequency;
QueryPerformanceFrequency(&frequency);
srand(time(0));
cout<<"输入商品数量n和背包容量W:";
cin>>n>>W;
for(int i=1;i<=n;i++)
{
w[i]=rand()%1000;
v[i]=rand()%1000;
}
cout<<"商品的重量和价值如下:"<<endl;
for(int i=1;i<=n;i++)
{ cout<<w[i]<<"\t";
cout<<v[i]<<endl;
}
QueryPerformanceCounter(&begin);
Knaspack();//构造矩阵
Traceback(); //求出解的向量数组
QueryPerformanceCounter(&end);
inttotalW=0;
inttotalV=0;
//显示可以放入的物品
cout<<"所选择的商品如下:"<<endl;
cout<<"序号i:\t重量w:\t价格v:\t"<<endl;
for(int i=1; i <= n ; i++)
{
if(best[i]== 1)
{
totalW+=w[i];
totalV+=v[i];
cout<<setiosflags(ios::left)<<setw(5)<<i<<" "<<w[i]<<"\t"<<v[i]<<endl;
}
}
cout<<"放入背包的物品总重量为:"<<totalW<<endl<<"放入背包的物品总价值为:"<<""<<totalV<<endl;
cout<<"时间:"
<<(double)(end.QuadPart- begin.QuadPart) / frequency.QuadPart
<<"s"<<endl;
}
2.4 运行结果及分析
上述测试数据中商品数量为4件,且书包的容量为8,算法执行过程中对m(i,j)填表如下所示:
3 回溯法Traceback()方法中由于v[1][8]==v[2][8]所以商品1不放入背包中,同理商品2也不放入背包中。而v[3][8]=9,v[4][8]=2,两者不相等,所以商品3放入背包中,且由于v[4][1]非负,所以商品4也放入背包中去,于是,得到了所要求的解。
3.1 0-1背包问题的实现
回溯法是一种系统地搜索问题解答的方法。为了实现回溯,首先需要为问题定义一个解空间,这个解空间必须至少包含问题的一个解(可能是最优的)。一旦定义了解空间的组织方要选择一个对象的子集,将它们装人背包,以便获得的收益最大,则解空间应组织成子集树的形状。首先形成一个递归算法,去找到可获得的最大收益。然后,对该算法加以改进,形成代码。改进后的代码可找到获得最大收益时包含在背包中的对象的集合。
左子树表示一个可行的结点,无论何时都要移动到它,当右子树可能含有比当前最优解还优的解时,移动到它。一种决定是否要移动到右子树的简单方法是r为还未遍历的对象的收益之和,将r加到cp(当前节点所获收益)之上,若( r+cp) <=bestp(目前最优解的收益),则不需搜索右子树。一种更有效的方法是按收益密度vi/wi对剩余对象排序,将对象按密度递减的顺序去填充背包的剩余容量。
3.2 编程实现如下
#include"stdafx.h"
#include<iostream>
#include<algorithm>
#include<time.h>
#include<Windows.h>
using namespace std;
#defineN 100 //最多可能物体数
structgoods //物品结构体
{
int sign; //物品序号
int w; //物品重量
int p; //物品价值
}a[N],b[N];
boolm(goods a,goods b)
{
return (a.p/a.w)>(b.p/b.w);
}
intmax1(int a,intb)
{
return a<b?b:a;
}
intn,W,bestP=0,cp=0,cw=0;
intX[N],cx[N];
intBackTrack(int i)
{
if(i>n-1){
if(bestP<cp){
for (int k=0;k<n;k++) X[k]=cx[k];//存储最优路径
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);
return bestP;
}
void KnapSack3(intn,goods a[],int C,intx[])
{
int totalW=0;
for(inti=0;i<n;i++)
{
x[i]=0;
a[i].sign=i;
}
sort(a,a+n,m);//将各物品按单位重量价值降序排列
BackTrack(0);
cout<<"所选择的商品如下:"<<endl;
cout<<"序号i:\t重量w:\t价格v:\t"<<endl;
for(int i=0;i<n;i++)
{
if(X[i]==1){
cout<<i+1<<"\t";totalW+=b[i].w;
cout<<b[i].w<<"\t";
cout<<b[i].p<<endl;
}
}
cout<<"放入背包的物品总重量为:"<<totalW;
cout<<endl;
cout<<"放入背包的物品总价值为:"<<bestP<<endl;
}
intmain()
{
LARGE_INTEGER begin,end,frequency;
QueryPerformanceFrequency(&frequency);
srand(time(0));
cout<<"请输入物品种数n和背包容量W:";
cin>>n>>W;
for (inti=0;i<n;i++)//输入物品i的重量w及其价值v
{
a[i].w=rand()%1000;
a[i].p=rand()%1000;
b[i]=a[i];
}
cout<<"物品的重量和价值分别如下:"<<endl;
for (inti=0;i<n;i++)//输入物品i的重量w及其价值v
{
cout<<a[i].w;
cout<<"\t";
cout<<a[i].p<<endl;
}
QueryPerformanceCounter(&begin);
KnapSack3(n,a,W,X);//调用回溯法求0/1背包问题
QueryPerformanceCounter(&end);
cout<<"时间:"
<<(double)(end.QuadPart- begin.QuadPart) / frequency.QuadPart
<<"s"<<endl;
}
3.3 运行结果
4 分枝-限界法
4.1 分枝-限界法的基本原理与分析
分枝限界发是另一种系统地搜索解空间的方法,它与回溯法的主要区别在于对E-结点的扩充方式。每个活结点有且仅有一次会变成E-结点。当一个结点变为E-结点时,则生成从该结点移动一步即可到达的所有新结点。在生成的结点中,抛弃那些不可能导出最优解的结点,其余结点加人活结点表,然后从表中选择一个结点作为下一个E结点。从活结点表中取出所选择的结点并进行扩充,直到找到解或活动表为空,扩充才结束。
4.2 0-1背包问题的实现
0-1背包问题的最大收益分枝定界算法可以使用定界函数来计算活结点的收益上限upprofit,使得以活结点为根的子树中的任一结点的收益值都不可能超过upprofit,活结点的最大堆使用upprofit作为关键值域。在子集树中执行最大收益分枝定界搜索的函数首先初始化活结点的最大堆,并使用一个数组bestx来记录最优解。由于需要不断地利用收益密度来排序,物品的索引值会随之变化,因此必须将函数所生成的结果映射回初始时的物品索引。函数中的循环首先检验E-结点左孩子的可行性,如它是可行的,则将它加入子集树及活结点队列(即最大堆),仅当结点右子树的定界值指明可能找到一个最优解时才将右孩子加入子集树和队列中。
4.3 编程实现如下
#include "stdafx.h"
#include<iostream>
#include<Windows.h>
#include<time.h>
#include<Windows.h>
#include<algorithm>
using namespacestd;
#define N 100 //最多可能物体数
struct goods //物品结构体
{
int sign; //物品序号
int w; //物品重量
int p; //物品价值
}a[N];
goodsb[N];
bool m(goods a,goods b)
{
return(a.p/a.w)>(b.p/b.w);
}
int max1(inta,int b)
{
returna<b?b:a;
}
int n,W,bestP=0,cp=0,cw=0;
int X[N],cx[N];
struct KNAPNODE //状态结构体
{
bools1[N]; //当前放入物体
int k; //搜索深度
int b; //价值上界
int w; //物体重量
int p; //物体价值
};
struct HEAP //堆元素结构体
{
KNAPNODE *p;//结点数据
intb; //所指结点的上界
};
//交换两个堆元素
void swap(HEAP &a, HEAP &b)
{
HEAP temp = a;
a = b;
b = temp;
}
//堆中元素上移
void mov_up(HEAP H[], int i)
{
bool done= false;
if(i!=1){
while(!done&& i!=1){
if(H[i].b>H[i/2].b){
swap(H[i], H[i/2]);
}else{
done = true;
}
i = i/2;
}
}
}
//堆中元素下移
void mov_down(HEAP H[], int n, int i)
{
bool done= false;
if((2*i)<=n){
while(!done&& ((i = 2*i) <= n)){
if(i+1<=n&& H[i+1].b > H[i].b){
i++;
}
if(H[i/2].b<H[i].b){
swap(H[i/2], H[i]);
}else{
done = true;
}
}
}
}
//往堆中插入结点
void insert(HEAP H[], HEAP x, int &n)
{
n++;
H[n] = x;
mov_up(H,n);
}
//删除堆中结点
void del(HEAP H[], int&n,int i)
{
HEAP x, y;
x = H[i]; y = H[n];
n --;
if(i<=n){
H[i] = y;
if(y.b>=x.b){
mov_up(H,i);
}else{
mov_down(H, n, i);
}
}
}
//获得堆顶元素并删除
HEAPdel_top(HEAP H[], int &n)
{
HEAP x = H[1];
del(H, n, 1);
return x;
}
//计算分支节点的上界
void bound( KNAPNODE* node, int M, goods a[], intn)
{
int i =node->k;
float w =node->w;
float p =node->p;
if(node->w>M){ // 物体重量超过背包载重量
node->b = 0; // 上界置为0
}else{
while((w+a[i].w<=M)&&(i<n)){
w += a[i].w; // 计算背包已装入载重
p += a[i++].p; // 计算背包已装入价值
}
if(i<n){
node->b = p + (M -w)*a[i].p/a[i].w;
}else{
node -> b = p;
}
}
}
//用分支限界法实现0/1背包问题
voidKnapSack4(int n,goods a[],int C,int X[])
{
int i, k= 0,totalW=0; // 堆中元素个数的计数器初始化为0
int v;
KNAPNODE *xnode, *ynode, *znode;
HEAP x, y, z, *heap;
heap = newHEAP[n*n]; // 分配堆的存储空间
for( i=0;i<n; i++){
a[i].sign=i; //记录物体的初始编号
}
sort(a,a+n,m); // 对物体按照价值重量比排序
xnode = newKNAPNODE; // 建立父亲结点
for( i=0;i<n; i++){ // 初始化结点
xnode->s1[i] = false;
}
xnode->k = xnode->w = xnode->p =0;
while(xnode->k<n){
ynode = newKNAPNODE; //建立结点y
*ynode = *xnode; //结点x的数据复制到结点y
ynode->s1[ynode->k] = true; // 装入第k个物体
ynode->w +=a[ynode->k].w; // 背包中物体重量累计
ynode->p += a[ynode->k].p; // 背包中物体价值累计
ynode->k ++; // 搜索深度++
bound(ynode, C, a, n); // 计算结点y的上界
y.b = ynode->b;
y.p = ynode;
insert(heap, y, k); //结点y按上界的值插入堆中
znode = newKNAPNODE; //建立结点z
*znode = *xnode; //结点x的数据复制到结点z
znode->k++; // 搜索深度++
bound(znode, C, a, n); //计算节点z的上界
z.b = znode->b;
z.p = znode;
insert(heap, z, k); //结点z按上界的值插入堆中
deletexnode;
x = del_top(heap, k); //获得堆顶元素作为新的父亲结点
xnode = x.p;
}
v = xnode->p;
for( i=0;i<n; i++){ //取装入背包中物体在排序前的序号
if(xnode->s1[i]){
X[a[i].sign] =1 ;
}else{
X[a[i].sign] = 0;
}
}
deletexnode;
deleteheap;
cout<<"所选择的商品如下:"<<endl;
cout<<"序号i:\t重量w:\t价格v:\t"<<endl;
for(int i=0;i<n;i++)
{
if(X[i]==1)
{totalW+=b[i].w;
cout<<i+1<<"\t";
cout<<b[i].w<<"\t";
cout<<b[i].p<<endl;
}
}
printf("放入背包的物品总价值为:%d\n",v);
cout<<"放入背包的物品总重量为:"<<totalW<<endl;
}
/*测试以上算法的主函数*/
int main()
{
LARGE_INTEGER begin,end,frequency;
QueryPerformanceFrequency(&frequency);
srand(time(0));
cout<<"请输入物品种数n和背包容量W:";
cin>>n >>W;
for (int i=0;i<n;i++) //输入物品i的重量w及其价值v
{
a[i].w=rand()%1000;
a[i].p=rand()%1000;
b[i]=a[i];
}
cout<<"物品的重量和价值分别如下:"<<endl;
for (inti=0;i<n;i++)//输入物品i的重量w及其价值v
{
cout<<a[i].w;
cout<<"\t";
cout<<a[i].p<<endl;
}
QueryPerformanceCounter(&begin);
KnapSack4(n,a,W,X);//调用分支限界法求0/1背包问题
QueryPerformanceCounter(&end);
cout<<"时间:"
<<(double)(end.QuadPart- begin.QuadPart) / frequency.QuadPart
<<"s"<<endl;
return 0;
}
4.4 运行结果及分析
三、四种算法分析与比较
1 算法的效率分析
(1)贪心法
贪心算法总是作出在当前看来是最好的选择,即贪心算法并不从整体最优解上加以考虑,它所作出的选择只是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广的许多问题它能产生整体最优解。在一些情况下,即使贪心算法不能得到整体最优解,但其最终结果却是最优解的很好近似解。贪心算法的时间复杂度为O(nlogn)。
(2)动态规划法
从m(i,j)的递归式容易看出,算法Knaspack需要O(nc)计算时间; Traceback需O(n)计算时间;算法总体需要O(nc)计算时间。
(3)回溯法
由于计算上界函数需要O(n)时间,在最坏情况下有个右孩子结点需要上界函数,故计算0-1背包问题的回溯算法所需的计算时间复杂度为。对回溯法的改进主要是对判断是否移动右子树上,一种更有效的方法是按效益密度vi/wi对剩余对象排序,将对象按密度递减的顺序去填充背包的剩余容量,当遇到第一个不能全部放人背包的对象时,就使用它的一部分。
回溯算法的运行时间取决于它在搜索过程中所生成的结点数,而限界函数可以大量减少所生成的结点个数,省去许多无谓的搜索, 使得搜索速度更快,其调用限界函数计算上界需花费O(n)时间 ,最坏情况下有个结点需调用限界函数 ,需花费O(n)时间,所以该算法的时间复杂度为。
(4) 分枝-限界法
分支限界法求解0/1背包问题的时间复杂度为:
为了直观表示,特将四种算法的时间复杂度总结如下:
2、对比实验结果
(1)为了进行对比,本程序采用相同的数据对不同算法进行测试,于是,程序中先在贪心算法中,运用rand()函数随机产生了10组数据作为测试数据,其中第一个数据是物品的重量,第二个数据是物品的价值。将这10组数据通过文件操作写于D盘中名为“lcw.txt”的记事本文件中。然后其他三种算法再用文件操作读取数据,进行设计。
(2)为了能对设计的算法进行效率的比较,本文采用测试程序运行时间的函数,LARGE_INTEGER begin ,end, frequency, QueryPerformanceFrequency(&frequency);QueryPerformanceCounter(&begin), QueryPerformanceCounter(&end)等来对设计的算法进行时间的度量。
(3)如下图所示:
贪心算法运行结果:
动态规划算法运行结果:
回溯算法运行结果:
分支-限界算法运行结果:
(4)结果分析
由以上实验结果可以看出,对于相同的测试数据,不同的算法得到完全相同的实验结果,由此可见设计的算法是有效的只是运行的时间有区别,由于测试的数据量比较小,各种算法的差距没有体现出来,为此,接下来将对不同问题规模下算法的运行时间进行分析。
3(1)这一次物品的数量来到了100件,同时背包的容量为1000。
(2)从以上结果可以看出,对于同样的测试数据,四种的算法各有特点。随着背包可选物品的件数和背包容量的增大,用回溯法来解答时,所用时间显著的增加,达到了21秒多,这显然是不希望看到的。同时也看见贪心解法在此种情况下得到的物品总价值是8373,而其他三种算法得到的物品总价值是8392。显然贪心算法在此种问题规模相对较大时,没能得到全局的最优解,只是得到局部的最优解,这和理论分析也是一致的。
4(1)为了更好地说明问题,现在对不同问题规模三种不同算法所需要的时间进行比较。随着问题规模的增大,各算法的计算时间都在增大,由于回溯法相对于其他算法所增加的时间更加显著,特此,单独考虑回溯法的情况。
(2)此种情况下背包的容量为100,不同问题规模回溯法所用时间所下表所示
5 由以上测试时间可以很好的验证,当背包容量和问题规模达到一定程度时,用回溯法解决背包问题,因此随着物件数n的增大,其解的空间将以 级增长,当n大到一定程度上,用此算法解决背包问题将是不现实的。这正好与理论分析的情况是一致的。
6 从实验中也可以发现,当问题规模很小的时候,四种算法都有较好的稳定性,计算时间都相差不多。随着问题规模的增大,各算法的计算时间差别逐渐显现出来。
7 对比以上四种算法可以看出,各种算法都有自己的特点,贪心算法速度比较快,但是所得的解有时可能只是局部最优解;分枝限界法需要的解空间。故该算法不常用在背包问题求解;回溯法比分枝限界在占用内存方面具有优势。回溯法占用的内存是0(解空间的最大路径长度),而分枝限界所占用的内存为0(解空间大小)。对于一个子集空间,回溯法需要0(n)的内存空间,而分枝限界则需要的空间。虽然最大收益或最小耗费分枝限界在直觉上要好于回溯法,并且在许多情况下可能会比回溯法检查更少的结点,但在实际应用中,它可能会在回溯法超出允许的时间限制之前就超出了内存的限制。