背包问题的递归形式解
2017-02-13 16:22
260 查看
背包问题是学习算法和数据结构时肯定会接触到的,我老早就了解到这个问题,可直到今天看到《挑战》书上才详细了解这个问题.
该问题的题设和要求如上。
拿到这个问题,最先想到的思路就是利用递归针对每个物品是否放入背包进行两种情况下的搜索。详细的源码和解释如下:#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define Max_N 100
int n;
int sumWeight;
int value[Max_N];
int weight[Max_N];
//从位置为index的物品进行背包问题,留给它们的重量为leftWeight,返回这种情况下能装的最大价值物品的总价值
int solve1(int index,int leftWeight)
{
int res=0;
if(index>=n) return 0; //物品已用完,剩下的重量装不了物品,返回价值0
if(weight[index]>leftWeight) //当前位置的物品重量大于剩余重量,则不选择该物品,从它下一个物品进行背包问题
res=solve1(index+1,leftWeight);
else
//还剩物品,并且当前位置的物品重量小于剩余重量,则就取该物品和不取该物品两种情况进行递归
res=max(solve1(index+1,leftWeight),solve1(index+1,leftWeight-weight[index])+value[index]);
return res;
}
int main()
{
cin>>n>>sumWeight;
for(int i=0;i<n;i++)
{
cin>>weight[i]>>value[i];
}
//初始化递归条件,从下标为0的物品开始背包问题,剩给它们的重量就是总重量 sumWeight
cout<<"背包问题solve1()最大总价值:"<<solve1(0,sumWeight);
return 0;
}
上述程序的问题就在于其效率比较低,针对每个物品都分为两种情况下的分支进行递归,这样其复杂度就为O(2^n),那么能不能优化呢?答案是可以的。
我们首先可以简单地把这种递归树用图的形式表达出来(下面每个单元中的数字就对应递归函数solve1()中的两个参数):
;
我们可以看到在参数为(3,2)时,其之后的递归情况是不变的,那么我们这时候在第一次遇到参数为(3,2)的递归时,将其返回的结果记录下来,下次再遇到参数为(3,2)的递归时,直接获得已经存储的结果就行了,这样就不用继续递归。这样看来,每种参数形式,都最多只会调用一次,所以对于n*sumWeight种组合的参数形式,上述程序的复杂度只有O(n*sumWeight)。详细代码如下:#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<memory.h>
using namespace std;
#define Max_N 100
#define Max_M 10000
int n;
int sumWeight;
int value[Max_N];
int weight[Max_N];
int dp[Max_N][Max_M];
//从位置为index的物品进行背包问题,留给它们的重量为leftWeight,返回这种情况下能装的最大价值物品的总价值
int solve2(int index,int leftWeight)
{
//记忆化搜索,如果之前已经对这种递归情况搜索过,那么直接返回值
if(dp[index][leftWeight]>=0)
return dp[index][leftWeight];
int res=0;
if(index>=n) return 0; //物品已用完,剩下的重量装不了物品,返回价值0
if(weight[index]>leftWeight) //当前位置的物品重量大于剩余重量,则不选择该物品,从它下一个物品进行背包问题
res=solve2(index+1,leftWeight);
else
res=max(solve2(index+1,leftWeight),solve2(index+1,leftWeight-weight[index])+value[index]);
//为了进行递归话搜索,将这种情况下的递归结果存储起来
return dp[index][leftWeight]=res;
//还剩物品,并且当前位置的物品重量小于剩余重量,则就取该物品和不取该物品两种情况进行递归
}
int main()
{
cin>>n>>sumWeight;
for(int i=0;i<n;i++)
{
cin>>weight[i]>>value[i];
}
//初始化记忆化搜索的数组,使用 memset将dp数组全部初始化为-1,具体memeset的详细使用,这里不阐述,就记住只能初始化为0或-1即可
//使用 memset需要导入 memory.h或者string.h头文件
memset(dp,-1,sizeof(dp));
//初始化递归条件,从下标为0的物品开始背包问题,剩给它们的重量就是总重量 sumWeight
cout<<"背包问题solve2()最大总价值:"<<solve2(0,sumWeight);
return 0;
}
这种形式下的程序复杂度对于题目中的数据规模已经完全足够了。上述这种带有存储的搜索递归形式叫做记忆化搜索。
当然,想这种递归式搜索遍历的的函数,其参数是不固定。上述两种代码只是拥有两个参数,现在我们把当前已经装好的物品的总价值也当做参数,可以得到如下的程序:
#include<iostream> #include<algorithm> #include<cstdlib> using namespace std; #define Max_N 100 int n; int sumWeight; int value[Max_N]; int weight[Max_N]; //从位置为index的物品进行背包问题,留给它们的重量为leftWeight,之前0~index-1物品的背包问题的总价值为curValue //返回这种情况下能装的最大价值物品的总价值 int solve3(int index,int leftWeight,int curValue) { int res=0; if(index>=n) return curValue; //物品已用完,剩下的重量装不了物品,返回当前总价值 if(weight[index]>leftWeight) //当前位置的物品重量大于剩余重量,则不选择该物品,从它下一个物品进行背包问题 res=solve3(index+1,leftWeight,curValue); else res=max(solve3(index+1,leftWeight,curValue),solve3(index+1,leftWeight-weight[index],curValue+value[index])); return res; //还剩物品,并且当前位置的物品重量小于剩余重量,则就取该物品和不取该物品两种情况进行递归 } int main() { cin>>n>>sumWeight; for(int i=0;i<n;i++) { cin>>weight[i]>>value[i]; } //初始化递归条件,从下标为0的物品开始背包问题,剩给它们的重量就是总重量 sumWeight,当前总价值肯定为0,因为还没有物品被选中 //这种把参数情况写的比较全比较有利于进行递归式搜索下的剪枝 cout<<"背包问题solve3()最大总价值:"<<solve3(0,sumWeight,0); return 0; }
这种参数比较全的递归在需要剪枝的情况下们可以根据当前获得信息,不再由这条分支继续递归下去,直接返回。这样也可以提高程序效率。
PS:关于重点的动态规划下的背包问题的解决方案在下篇博文中会介绍~
相关文章推荐
- 背包问题(递归)
- 0-1背包问题经典算法(递归实现)
- 递归实现背包问题(C#)
- 简单的背包问题(非递归和递归)
- C++递归问题之三---0-1背包问题:给定两个值value和num,在1到num之间取值使这些数和为value,输出所有组合
- 背包问题的递归与非递归算法
- 背包问题选中递归求解0 1背包问题
- 简单的背包问题--java递归实现
- 0-1背包问题的递归实现与非递归实现
- 0/1背包问题(递归解决,递推解决)
- 0/1背包问题(递归与非递归)
- 0-1背包问题,非递归遍历
- 简单的[0/1]背包问题 分别用递归与非递归实现
- 动态规划:0-1背包问题(使用递归方法)
- 备忘录和递归解决背包问题
- 背包问题非递归实现
- 递归-背包问题
- 非递归形式的N皇后问题---用C#实现,VS2008可以执行
- 数据结构之非递归解决0-1背包问题
- 递归求解0 1背包问题