给定n种物品和一背包。物品i的重量是wi>0,其价值为vi>0,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? (要求使用回溯法)
步骤:
- 输入物品数量n;
- 依次对物品输入重量wi和价值vi;
- 将物品按照其单位重量价值从大到小排序;
- 构造解的子集树。对于每一个物品i,对于该物品只有选与不选2个决策,总共有n个物品,顺序依次考虑每个物品,这样就形成了一棵解空间树。基本思想就是遍历这棵树,以枚举所有情况,最后进行判断,如果重量不超过背包容量,且价值最大的话,该方案就是最后的答案。
- 在递归函数Backtrack中,当i>n时,算法搜索至叶子结点,得到一个新的物品装包方案。此时算法适时更新当前的最优价值。当i<n时,当前扩展结点位于排列树的第(i-1)层,此时算法选择下一个要安排的物品,以深度优先方式递归的对相应的子树进行搜索,对不满足上界约束的结点,则剪去相应的子树。
- 在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其剪枝。
实验原理:
回溯法是一种非常有效的方法,有“通用的解题法”之称。它有点像穷举法,但是更带有跳跃性和系统性,他可以系统性的搜索一个问题的所有的解和任一解。回溯法采用的是深度优先策略。
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
实验程序:
knapsack算法
这个算法相较于实现1,有效避免了不必要及比较过程,当j<w[i]的时候直接填充m[i+1][j]而不需要比较,另外由于最后一行的前c-1个数据并没有使用,所以无需计算。
int knapsack(int m[][maxn],int n,int c)
{
int jMax=min(w[n],c);
//预处理第一行,j小于w[n]填充物为0,否则填充为v[n]
for(int j=0;j<jMax;j++) m[n][j]=0;
for(int j=jMax;j<c;j++) m[n][j]=v[n];
for(int i=n-1;i>1;i--)//最后一行不计算,因为有一部分填充是无效的
{
jMax=min(w[i],c);
for(int j=0;j<jMax;j++)
m[i][j]=m[i+1][j];//不选
for(int j=jMax;j<c+1;j++)
m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);//处理最后一行的最后一个数据
return m[1][c];
}
求解最优解的选取方式traceBack回溯
形成以上的一组表格之后我们可以很容易得到最优解m[1][c],但是如何求解选取方式呢?我们仍然可以根据递推式回溯:
如果m[i][c]==m[i+1][c],说明i号物品并没有被选取,i++
如果m[i][c]!=m[i+1][c],说明i号物品被选取,i++,c-wi
当i=n时,如果m[n][c]!=0说明n号物品被选取,否则未被选取
void traceBack(int m[maxn][maxn],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-=w[i];
}
}
x[n]=(m[n][c])?1:0;
}
总代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
const int maxn=1005;
int m[maxn][maxn]={0};
int w[maxn]={0};
int v[maxn]={0};
int dpFunc1(int m[][maxn],int n,int c)
{
for(int i=0;i<n+1;i++) m[i][0]=0;//初始化第0列
for(int j=0;j<c+1;j++) m[0][j]=0;//初始化第0行
for(int i=n;i>=1;i--)//序号
{
for(int j=1;j<=c;j++)//背包剩余容量
{
if(j>=w[i]) m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
else m[i][j]=m[i+1][j];
}
}
return m[1][c];//返回最优解
}
int knapsack(int m[][maxn],int n,int c)
{
int jMax=min(w[n],c);
//预处理第一行,j小于w[n]填充物为0,否则填充为v[n]
for(int j=0;j<jMax;j++) m[n][j]=0;
for(int j=jMax;j<c;j++) m[n][j]=v[n];
for(int i=n-1;i>1;i--)//最后一行不计算,因为有一部分填充是无效的
{
jMax=min(w[i],c);
for(int j=0;j<jMax;j++)
m[i][j]=m[i+1][j];//不选
for(int j=jMax;j<c+1;j++)
m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);//处理最后一行的最后一个数据
return m[1][c];
}
void traceBack(int m[maxn][maxn],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-=w[i];
}
}
x[n]=(m[n][c])?1:0;
}
int main()
{
int n,c,x[maxn]={0};
cin>>n>>c;
for(int i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
int res=dpFunc1(m,n,c);//函数调用
cout<<res<<endl;
res=knapsack(m,n,c);
cout<<res<<endl;
traceBack(m,w,c,n,x);
for(int i=1;i<n+1;i++) cout<<i<<' '<<x[i]<<endl;
system("pause");
return 0;
}
实验测试:
复杂度分析:
时间复杂度:T(n)=O(n^2)
空间复杂度:S(n)=O(n^2)