实验八:0-1 背包问题(动态规划)报告
2017061111 李静娴
1.问题描述
给定 n 种物品和一背包。物品 i 的重量是 wi,其价值为 vi,背包的容量为 C。问:应如何
选择装入背包的物品,使得装入背包中物品的总价值最大?
2.实验目的
(1)熟悉动态规划算法,并学以致用
(2)熟练掌握 0-1 背包问题算法
3.实验原理
设所给 0-1 背包问题的子问题的最优值为 m(i,j),即 m(i,j)是背包容量为 j,可选择物
品为 i,i+1,…,n 时 0-1 背包问题的最优值。由 0-1 背包问题的最优子结构性质,可以建
立计算 m(i,j)的递归式:
时间为
算法时间复杂度分析:
q[i+1]需要 O(|p[i+1]|)计算时间。合并 p[i+1]和 q[i+1]并清除受控跳跃点也需要 O(|p[i+1]|)计
算时间。从跳跃点集 p[i]的定义可以看出,p[i]中的跳跃点相应于 xi,…,xn 的 0/1 赋值。因此,
算法的主要计算量在于计算跳跃点集 p[i](1≤i≤n)。由于 q[i+1]=p[i+1]⊕(wi,vi),故计算
p[i]中跳跃点个数不超过 2^(n-i+1)。由此可见,算法计算跳跃点集 p[i](1≤i≤n)所花费的计算
|p[i]|≤c+1,其中,1≤i≤n。在这种情况下,改进后算法的计算时间复杂性为 O(min{nc,2^n}。
从而,改进后算法的计算时间复杂性为 O(2^n)。当所给物品的重量 wi 是整数时,
4.实验设计
1]|)= (
O
i
2
n i
2 )
i
2
O
(
O
n
(2 )
n
|
[
p i
n
(1)
输入数据格式:输入背包的容量和物品的个数,要求都为整型的数
生成方式:在输入数据规模之后,程序中先生成基于当前时间的随机数种子,然后通过 for
循环语句,每次用随机函数生成一个范围在 1-30 的随机数并将其放入物品重量数组 w[]中。
同样的方式生成范围在 1-100 的随机数并将其放入物品重量数组 v[]中。
数据规模:用户手动输入数据规模。
(2)
该程序先是基于当前系统时间生成随机数种子,再根据输入的数据规模创建两个相应大
小的整型数组,然后通过 for 循环语句和随机函数 rand()循环生成数据规模个数的元素并存
入创建的数组中。然后调用 knapsack(n,w,v,c,p,head,x)函数,并在调用函数的前后使用 clock()
函数记入时间,用于计算算法的运行时间并输出。同时,在 knapsack(n,w,v,c,p,head,x)函数里
输出装入的物品序列。
(3)
程序运行的结果有五个,先是打印物品的重量列表 w,接着打印物品的价值列表 v,然
后最优值,即在背包容量允许下能装入的物品最大价值,再输出最优解,即选择的物品方案,
最后输出算法运行时间。
5.实验结果与分析
由此可见程序运行结果无误。
接下来再加几个测试用例,以便验证算法时间按复杂度。
测试结果:
背包容量 c 物品个数 n
5
10
20
100
100
100
nc
2^n
min{nc,2^n} 算法运行时间(单位:秒)
500
1000
2000
32
1024
1048576
32
1000
2000
0.33
0.336
0.343
在一定的误差允许范围内,可见算法运行时间与 min{nc,2^n}成线性关系。
6.结论
我用随机数方法给物品重量数组和物品价值数组赋值,这样节省了人力。为了使程序更
具有交互性,我选择动态生成数组。这里 p[][]我选择了 vector 的方式,w[]和 v[]我选择堆
方式。程序中 p[][0]存储已经装入物品的总重量,p[][1]存储已经装入物品的总价值,这里,
在定义声明时我需要确定 vector p[][]的维度,我经过推理后得到,n 个物品 p 最多有 2^n
项,因此,当我限制 n 最大为 20 时,p 的维数就可以写成这样:p[1048576][2],最后,我
经过 excel 工具处理分析数据得出结论,算法运行时间与 min{nc,2^n}成线性关系。
7.程序源码
#include
#include
#include
#include
#include
using namespace std;
void knapsack(int n,double w[],double v[],int c,vector> p,int head[],int x[]){
head[n+1]=0;
p[0][0]=0;//p[][0]存储物品重量
p[0][1]=0;//p[][1]存储物品价值,物品 n 的跳跃点(0,0)
int left=0,right=0,next=1;//left 指向 p[i+1]的第一个跳跃点,right 指向最后一个
//next 即下一个跳跃点要存放的位置
head[n]=1;//用来指向第 n 个物品第一个跳跃点的位置
for(int i=n;i>=1;i--){
int k=left;//k 指向 p[]中跳跃点,移动 k 来判断 p[]与 p[]+(wv)中的受控点
for(int j=left;j<=right;j++){
if(p[j][0]+w[i]>c) break;//剩余的空间不能再装入 i,退出 for 循环
double y=p[j][0]+w[i],m=p[j][1]+v[i];//计算 p[]+(wv)
//若 p[k][0]较小则(p[k][0],p[k][1])一定不是受控点,将其作为 p[i]的跳跃点存储
while(k<=right&&p[k][0]p[next-1][1]){
p[next][0]=y;
p[next++][1]=m;
}
//若是,则对下一个元素进行判断
while(k<=right&&p[k][1]<=p[next-1][1]) k++;
}
while(k<=right){
p[next][0]=p[k][0];
p[next++][1]=p[k++][1];//将 i+1 剩下的跳跃点作为 i 的跳跃点存储
}
left=right+1;
right=next-1;
//第 i-1 个物品第一个跳跃点的位置,head[0]指第 0 个物品第一个跳跃点的位置
head[i-1]=next;
}
//x[]数组存储对应物品 0-1 向量,0 不装入背包,1 装入背包
//初始化 j,m 为最后一个跳跃点对应的第 0 列及第一列
double j=p[head[0]-1][0];
double m=p[head[0]-1][1];
for(int i=1;i<=n;i++){
x[i]=0;//初始化数组
for(int k=head[i+1];k<=head[i]-1;k++){//初始 k 指向 p[2]的第一个跳跃点(0,0)
//判断物品 i 是否装入
if(p[k][0]+w[i]==j&&p[k][1]+v[i]==m){
x[i]=1;//物品 i 被装入,则 x[i]置 1
j=p[k][0];//j 和 m 置为满足 if 条件的跳跃点对应的值
m=p[k][1];
break;//再接着判断下一个物品
}
}
}
cout<<"最优值是:"<
>c;
cout<<"自定义物品个数(<=20):"<cin>>n;
double *w=new double[n+1];
double *v=new double[n+1];
int *head=new int[n+2];
int *x=new int[n+1];
vector
> p(1048576);//经推理得,p[][]的第一维最多有 2^n,n 为物品个
数
for(int i=0;i