0
点赞
收藏
分享

微信扫一扫

算法:0-1背包(跳跃点解法)


算法:0-1背包(跳跃点解法)_0-1背包


算法:0-1背包(跳跃点解法)_0-1背包_02


算法:0-1背包(跳跃点解法)_i++_03


由m(i,j)的递归式容易证明,在一般情况下,对每一个确定的i(1≤i≤n),函数m(i,j)是关于变量j的阶梯状单调不减函数。跳跃点是这一类函数的描述特征。在一般情况下,函数m(i,j)由其全部跳跃点唯一确定。如图所示。

算法:0-1背包(跳跃点解法)_0-1背包_04


对每一个确定的i(1≤i≤n),用一个表p[i]存储函数m(i,j)的全部跳跃点。表p[i]可依计算m(i,j)的递归式递归地由表p[i+1]计算,初始时p[n+1]={(0,0)}。

一个例子:n=3,c=6,w={4,3,2},v={5,2,1}。

算法:0-1背包(跳跃点解法)_跳跃点解法_05


举个栗子:

n=5,c=10,w={2,2,6,5,4},v={6,3,5,4,6}。跳跃点的计算过程如下:

初始时p[6]={(0,0)}

因此,q[6]=p[6]⊕(w[5],v[5])={(4,6)}

p[5]={(0,0),(4,6)}

q[5]=p[5]⊕(w[4],v[4])={(5,4),(9,10)}

p[4]={(0,0),(4,6),(9,10)}   p[5]与q[5]的并集p[5]∪q[5]={(0,0),(4,6),(5,4),(9,10)}中跳跃点(5,4)受控于跳跃点(4,6)。将受控跳跃点(5,4)清除后,得到p[4]

q[4]=p[4]⊕(6,5)={(6,5),(10,11)}

p[3]={(0,0),(4,6),(9,10),(10,11)}

q[3]=p[3]⊕(2,3)={(2,3),(6,9)}

p[2]={(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]={(0,0),(2,6),(4,9),(6,12),(8,15)}

p[1]的最后的那个跳跃点(8,15)即为所求的最优值,m(1,C)=15

算法思想:

先在 p(i-1)的元素 j 上得到一个新状态,然后 w 小于它的不受影响,直接搬,w 等于它的,对 v 取大更,w 大于它的,根据 v 值直接pass 掉 不合法的点(w 大但 v 小于前边的),同时,出现了新状态往里写的时候,也要注意,w 大 v

#include<bits/stdc++.h>
using namespace std;

const int N = 5;

template<class Type>
int Knapsack(int n,Type c,Type v[],Type w[],int **p,int x[]);
template<class Type>
void Traceback(int n,Type w[],Type v[],Type **p,int *head,int x[]);

int main()
{
int c=10;
int v[]= {0,6,3,5,4,6},w[]= {0,2,2,6,5,4}; //下标从1开始
int x[N+1];

int **p = new int *[50];
for(int i=0; i<50; i++)
{
p[i] = new int[2];
}

cout<<"待装物品重量分别为:"<<endl;
for(int i=1; i<=N; i++)
{
cout<<w[i]<<" ";
}
cout<<endl;

cout<<"待装物品价值分别为:"<<endl;
for(int i=1; i<=N; i++)
{
cout<<v[i]<<" ";
}
cout<<endl;

cout<<"背包能装的最大价值为:"<<Knapsack(N,c,v,w,p,x)<<endl;

cout<<"背包装下的物品编号为:"<<endl;
for(int i=1; i<=N; i++)
{
if(x[i]==1)
{
cout<<i<<" ";
}
}
cout<<endl;

for(int i=0; i<50; i++)
{
delete p[i];
}

delete[] p;

return 0;
}

template<class Type>
int Knapsack(int n,Type c,Type v[],Type w[],int **p,int x[])
{
int *head = new int[n+2];//n+1个节点,记录跳跃点位置
head[n+1]=0;//对应p[6]

p[0][0]=0;//p[][0]存储物品重量
p[0][1]=0;//p[][1]存储物品价值,物品n的跳跃点(0,0)

// left 指向p[i+1]的第一个跳跃点,right指向最后一个
//拿书上的例子来说,若计算p[3]=0;则left指向p[4]的第一跳跃点(0 0)right指向(9,10)
int left = 0,right = 0,next = 1;//next即下一个跳跃点要存放的位置
head[n]=1;//head[n]用来指向第n个物品第一个跳跃点的位置

for(int i=n; i>=1; i--)
{
int k = left;//k指向p[ ]中跳跃点,移动k来判断p[]与p[]+(w v)中的受控点
for(int j=left; j<=right; j++)
{
if(p[j][0]+w[i]>c) break;//剩余的空间不能再装入i,退出for循环;
Type y = p[j][0] + w[i],m = p[j][1] + v[i];//计算p[ ]+(w v)

//若p[k][0]较小则(p[k][0] p[k][1])一定不是受控点,将其作为p[i]的跳跃点存储
while(k<=right && p[k][0]<y)
{
p[next][0]=p[k][0];
p[next++][1]=p[k++][1];
}

//如果 p[k][0]==y而m<p[k][1],则(y m)为受控点不存
if(k<=right && p[k][0]==y)
{
if(m<p[k][1])//对(p[k][0] p[k][1])进行判断
{
m=p[k][1];
}
k++;
}

// 若p[k][0]>=y且m> =p[k][1],判断是不是当前i的最后一个跳跃点的受控点
//若不是则为i的跳跃点存储
if(m>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[n]指第n个物品第一个跳跃点的位置
head[i-1] = next;
}
cout<<"head:"<<endl;
for(int i=0;i<=N+1;i++)
{
cout<<head[i]<<endl;
}
cout<<"p[6-1]:"<<endl;
for(int i=0;i<N+1;i++)
{
for(int j=head[i+1];j<=head[i]-1;j++)
cout<<p[j][0]<<","<<p[j][1]<<" ";
cout<<endl;
}

Traceback(n,w,v,p,head,x);
return p[next-1][1];
}

//x[]数组存储对应物品0-1向量,0不装入背包,1表示装入背包
template<class Type>
void Traceback(int n,Type w[],Type v[],Type **p,int *head,int x[])
{
//初始化j,m为最后一个跳跃点对应的第0列及第1列
//如上例求出的 最后一个跳跃点为(8 15)j=8,m=15
Type j = p[head[0]-1][0],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是否装入,如上例与跳跃点(6 9)相加等于(8 15)所以1装入
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];// 如上例j=6,m=9
break;//再接着判断下一个物品
}
}
}
}

运行效果:

算法:0-1背包(跳跃点解法)_初始化_06


举报

相关推荐

0 条评论