背包问题的递归形式解
来源:互联网 发布:淘宝pc转无线连接 编辑:程序博客网 时间:2024/04/20 15:20
背包问题是学习算法和数据结构时肯定会接触到的,我老早就了解到这个问题,可直到今天看到《挑战》书上才详细了解这个问题.
该问题的题设和要求如上。
拿到这个问题,最先想到的思路就是利用递归针对每个物品是否放入背包进行两种情况下的搜索。详细的源码和解释如下:
#include<iostream>#include<algorithm>#include<cstdlib>using namespace std;#define Max_N 100int 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的物品开始背包问题,剩给它们的重量就是总重量 sumWeightcout<<"背包问题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 10000int 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的物品开始背包问题,剩给它们的重量就是总重量 sumWeightcout<<"背包问题solve2()最大总价值:"<<solve2(0,sumWeight);return 0;}
这种形式下的程序复杂度对于题目中的数据规模已经完全足够了。上述这种带有存储的搜索递归形式叫做记忆化搜索。
当然,想这种递归式搜索遍历的的函数,其参数是不固定。上述两种代码只是拥有两个参数,现在我们把当前已经装好的物品的总价值也当做参数,可以得到如下的程序:
#include<iostream>#include<algorithm>#include<cstdlib>using namespace std;#define Max_N 100int 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:关于重点的动态规划下的背包问题的解决方案在下篇博文中会介绍~
1 0
- 背包问题的递归形式解
- 背包问题的动态规划形式解
- 背包问题的递归算法
- 背包问题的递归解法
- 背包问题的非递归解法
- 简单的背包问题--java递归实现
- 自己写的非递归背包问题
- p1054 简单背包问题的递归解法
- 关于背包问题的递归解法
- 背包问题的递归和非递归的解法
- 背包问题的递归和非递归的解法
- 背包问题的递归与非递归算法
- 背包问题的递归与非递归算法
- 简单的背包问题(非递归和递归)
- 简单背包问题的递归与非递归实现
- 背包问题的递归与非递归求解
- 递归算法 背包问题
- 背包问题(递归)
- CTime和CTimeSpan与COleDateTime和COleDateTimeSpan 类
- 堆外内存(直接内存)
- ABAP负号提前的测试
- 如何编辑pdf文件的背景内容
- Perf -- Linux下的系统性能调优工具(引用博客)
- 背包问题的递归形式解
- url 编码(percentcode 百分号编码)
- [译] 关于 Git 你需要知道的一些事情
- 错误宏定义ERR_EXIT
- spring boot整合redis实现缓存机制
- Spring 定时任务之 @Scheduled cron表达式
- Codeforces 635B Island Puzzle【最小表示法+思维】
- 第二篇、创建型设计模式——工厂方法模式
- iOS NSPredicate特殊字符判断无法正常使用