标签:index 优先 int DFS 选择 sumW 搜索 sumC
深度优先搜索会走遍所有路径,并且每次走到死胡同就代表一条完整路径的形成。这就是说,深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。
使用递归可以很好地实现深度优先搜索。当然也可以使用非递归的方法实现DFS,但是非递归的方法一般情况下会比递归的方法要麻烦。在使用递归的时候,系统还会调用一个叫系统栈的东西来存放递归中的每一层状态,因此使用递归来实现DFS的本质其实还是栈。
接下来讲解一个例子,读者需要从中理解其中包含的DFS思想,并尝试学习写出本例的代码。
题目:
有n件物品,每件物品的重量为w[i],价值为c[i]。现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值。(1≤n≤20)
思考:
在这个问题中,需要从n件物品中选择若干件物品放入背包,使它们的价值之和最大。这样的话,对每件物品都有选或者不选两种选择,而这就是所谓的“岔道口”。那么什么是“死胡同”呢?——题目要求选择的物品重量总和不能超过V,
因此一旦选择的物品重量总和超过 V ,就会到达“死胡同”,需要返回最近的“岔道口”。
显然,每次都要对物品进行选择,因此DFS函数的参数中必须记录当前处理的物品编号 index。而题目中涉及了物品的重量与价值,
因此也需要参数来记录在处理当前物品之前,已选物品的总重量 sumW 与总价值 sumC
函数的声明:应该为void DFS(int index,int sumW,int sumC);
于是,如果选择不放入index号物品,那么sumW与sumC就将不变,接下来处理 index + 1号物品,即前往DFS(index + 1, sumW, sumC)这条分支;
而如果选择放入index号物品,那么sumW将增加当前物品的重量 w[index],sumC 将增加当前物品的价值 c[index] ,接着处理index + 1号物品,
即前往DFS(index + 1, sumW + w[index], sumC + c[index])这条分支。
一旦index增长到了n,则说明已经把n件物品处理完毕(因为物品下标为从0到n - 1),此时记录的sumW和sumC就是所选物品的总重量和总价值。
如果sumW不超过V且sumC大于一个全局的记录最大总价值的变量 maxValue,就说明当前的这种选择方案可以得到更大的价值,于是用sumC更新maxValue。
下面的代码体现了上面的思路,请注意“岔道口”和“死胡同”在代码中是如何体现的:
#include<cstdio> const int maxn = 30; int w[maxn], c[maxn], V,maxValue=0,n;//maxValue为最大价值。 index为当前物品的编号,sumW,sumC为当前物品的总重量,和总价值。 void DES(int index, int sumW, int sumC) { if (index == n ) {//已经完成了对n件物品的选择 if (sumC > maxValue && sumW <= V) { maxValue = sumC; } return; } DES(index + 1, sumW, sumC); DES(index + 1, sumW + w[index], sumC + c[index]); } int main() { scanf("%d%d", &n, &V); for (int i = 0; i < n; i++) { scanf("%d%d", &w[i], &c[i]); } DES(0, 0, 0); printf("%d", maxValue); return 0; }
可以注意到,由于每件物品有两种选择,因此上面代码的复杂度为O(2^n),这看起来不是很优秀。
但是可以通过对算法的优化,来使其在随机数据的表现上有更好的效率。
在上述代码中,总是把n件物品的选择全部确定之后オ去更新最大价值,但是事实上忽视了背包容量不超过V这个特点。
也就是说,完全可以把对sumW的判断加入“岔道口”中,只有当sumW≤V时才进入岔道,这样效率会高很多,
代码如下,只更改了DFS:
void DES(int index, int sumW, int sumC) { if (index = n + 1) { return; } DES(index + 1, sumW, sumC);//不选第index件物品,这一语句也可以移动到,if 语句中. if (sumW + w[index] <= V) { if (sumC + c[index] > maxValue) { maxValue = sumC + c[index];//更新最大价值maxValue } DES(index + 1, sumW + w[index], sumC + c[index]);//选第index件物品 } }
可以看到,原先第二条岔路是直接进入的,但是这里先判断加入第index件物品后能否满足容量不超过V的要求,
只有当条件满足时才更新最大价值以及进入这条岔路,这样可以降低计算量,使算法在数据不极端时有很好的表现。
这种通过题目条件的限制来节省DFS计算量的方法称作剪枝(前提是剪枝后算法仍然正确)。
剪枝是一门艺术,学会灵活运用题目中给出的条件,可以使得代码的计算量大大降低,很多题目甚至可以使时间复杂度下降好几个等级。
事实上,上面的这个问题给出了一类常见DFS问题的解决方法,即给定一个序列,枚举
这个序列的所有子序列(可以不连续)。例如对序列**(1,2,3)来说,它的所有子序列为{ 1 }、{ 2 }、{ 3 }、{ 1,2 }、{ 1,3 }、{ 2,3 }、{ 1,2,3 }**
枚举所有子序列的目的很明显—一可以从中选择一个"最优"子序列,使它的某个特征是所有子序列中最优的;
如果有需要,还可以把这个最优子序列保存下来。显然,这个问题也等价于枚举从N个整数中选择K个数的所有方案。
例如这样一个问题:
给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X:如果有多种方案,选择它们中元素平方和最大的一个。
数据保证这样的方案唯一。
例如,从4个整数 { 2, 3, 3, 4 }中选择2个数。
使它们的和为6,显然有两种方案{ 2,4 }与(3, 3},其中平方和最大的方案为{ 2,4 }。
思考:
与之前的问题类似,此处仍然需要记录当前处理的整数编号 index;由于要求恰好选择K个数,因此需要一个参数 nowK 来记录当前已经选择的数的个数;
另外,还需要参数 sum 和 sumSqu 分别记录当前已选整数之和与平方和。于是 DFS 就是下面这个样子:
void DFS(int index, int nowK, int sum, int sumSqu){…}
此处主要讲解如何保存最优方案,即平方和最大的方案。首先,需要一个数组temp,用
以存放当前已经选择的整数。这样,当试图进入“选index号数”这条分支时,就把A[index]
加入temp中;而当这条分支结束时,就把它从temp中去除,使它不会影响“不选index号数”这条分支。
接着,如果在某个时候发现当前已经选择了K个数,且这K个数之和恰好为x时,就去判断平方和是否比已有的最大平方和maxSumSqu还要大;
如果确实更大,那么说明找到了更优的方案,把temp赋给用以存放最优方案的数组ans。这样,当所有方案都枚举完毕后,ans存放的就是最优方案,maxSumSqu存放的就是对应的最优值。
下面给出了主要部分的代码,建议读者能完整理解并自行写出 :
#include<cstdio> #include<vector> using namespace std; const int maxn = 30; int n, k, x, maxSumSqu = -1,A[maxn]; vector<int> temp, ans;//temp存放临时方案,ans存放平方和最大的方案 //当前处理index号整数,当前已选取的整数个数时nowK //当前已选整数之和为sum,当前已选整数平方和为sumSqu void DES(int index, int nowK, int sum, int sumSqu) { if (nowK == k && sum == x) { if (sumSqu > maxSumSqu) { maxSumSqu = sumSqu; ans = temp;//将temp赋值给ans } return; } if (nowK > k || sum > x || index == n) return; temp.push_back(A[index]); DES(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]); temp.pop_back();//这一步很重要,删除加入的元素 DES(index + 1, nowK, sum, sumSqu);//不选择第index个元素 } int main() { scanf("%d%d%d", &n, &k, &x); for (int i = 0; i <n; i++) { scanf("%d", &A[i]); } DES(0, 0, 0, 0); for (vector<int>::iterator it = ans.begin(); it != ans.end(); it++) { printf("%d ", *it);//输出最优子序列 } printf("%d", maxSumSqu);//输出最大的平方和 }
上面这个问题中的每个数都只能选择一次,现在稍微修改题目:
假设N个整数中的每个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X。
例如有三个整数1、4、7,需要从中选择5个数,使得这5个数之和为17。显然,只需要选择3个1和2个7,即可得到17。
这个问题只需要对上面的代码进行少量的修改即可。由于每个整数都可以被选择多次,因此当选择了 index 号数时,不应当直接进入 index + 1 号数的处理。
显然,应当能够继续选择 index 号数,直到某个时刻决定不再选择 index 号数,就会通过“不选 index号数”这条分支进入 index + 1 号数的处理。
因此只需要把“选index号数”这条分支的代码修改为
DFS(index, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
即可
标签:index,优先,int,DFS,选择,sumW,搜索,sumC 来源: https://www.cnblogs.com/migang/p/14671842.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。