深度优先搜索(DFS解决枚举所有子序列的问题)
用一个例子,理解其中包含的DFS思想。
有n件物品,每件物品的重量是w[i],价值是c[i]。现在需要选出若干件物品放入一个容量为V的背包中,
使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大值n在1到20之间。
在这个问题中,需要从n件物品中选择若干件物品放入背包中,使它们的价值之和最大。这样的话,每件物品都有选
或者不选两种选择,而这就是迷宫中的“岔道口”。那么什么是“死胡同”呢?——题目要求选择的物品重量之和不能超过V,
因此一旦选择的物品重量和超过V,就会达到“死胡同”,需要返回最近的“岔道口”。
显然,每次都需要对物品进行选择,因此DFS函数的参数中必须记录当前处理的物品编号index。而题目涉及了物品的重量
与价值,因此也需要参数来记录在处理当前物品之前,已选物品的总重量sumW与总价值sumC。于是DFS的函数大致是这样的……
1 void DFS(int index,int sumW,int sumC){
2 //终止条件(死胡同)
3
4
5 //岔道口,每次的选择.
6 }
于是,如果选择不放入index物品,那么sumW与sumC就将不变,接下来处理index+1号物品,即前往DFS(index+1,sumW,sumC)这条
分支;而如果选择放入index号物品,那么sumW将增加当前物品重量和当前物品价值,接着处理index+1号物品,即前往
DFS(index+1,sumW+w[index],sumC+c[index])这条分支。
一旦index增长到了n,则说明已经把n件物品都处理完了。根据题目要求更新记录的最大价值。
我们简单画一个数组图。
代码实现如下:
1 #include <stdio.h>
2
3
4 #define maxn 30
5
6 int maxPrice = 0;//全局变量,最大价值
7 int n,V;
8 int w[maxn],c[maxn];
9
10 void DFS(int index,int sumW,int sumC);
11
12 int main(){
13
14
15 scanf("%d%d",&n,&V);//提供n件物品和一个容量为Vkg的背包
16
17 for(int i=0;i<n;i++){
18 scanf("%d",w+i);//输入每件物品的重量
19 }
20
21 for(int i=0;i<n;i++){
22 scanf("%d",c+i);//输入每件物品的价值
23 }
24
25 DFS(0,0,0);
26 printf("%d\n",maxPrice);
27
28
29 return 0;
30 }
31
32
33 void DFS(int index,int sumW,int sumC){
34 if(index == n){//已经完成对n件物品的选择(死胡同)
35 if(sumW <= V&&sumC > maxPrice){
36 maxPrice = sumC;
37 }
38
39 return ;
40 }
41
42 //岔道口
43 DFS(index+1,sumW,sumC); //不选择第index件物品
44 DFS(index+1,sumW+w[index],sumC+c[index]); //选择第index件物品
45 }
可以注意到,由于每件物品都有两种选择,因此上面代码的复杂度为O(2n),这看起来不是很优秀。但是可以通过算法进行优化,
使其表现出更好的效率。还记得我们之前说的“死胡同”吗?上面的代码把index == n作为一个“死胡同”,使得计算量很大,其实
在这之前我们已经遇到“死胡同”了,因为在计算过程中必定存在sumW已经超过题目限定的V这种可能。所以我们可以在“岔道口”判断
下一步是否是“死胡同”。
1 void DFS(int index,int sumW,int sumC){
2 if(index == n){//已经完成对n件物品的选择(死胡同)
3 return ;
4 }
5
6 //岔道口
7 DFS(index+1,sumW,sumC); //不选择第index件物品
8 //只有加入index件物品后未超过容量V,才能继续探索
9 if(sumW + w[index] <= V){
10 //注意更新最大价值
11 if(sumC + c[index] > maxPrice){
12 maxPrice = sumC + c[index];
13 }
14 DFS(index+1,sumW+w[index],sumC+c[index]); //选择第index件物品
15 }
16
17 }
可以看到,原先第二条岔路是直接进入的,但是这里先判断加入第index件物品后是否满足容量不超过V的要求(“死胡同”),
只有当条件满足时,才更新最大价值以及进入这条岔路。这样可以降低计算量,使算法在数据不极端时有很好的表现。这种通过
题目条件的限制来节省DFS计算量的方法称作剪枝。
事实上,上面的问题给出了一类常见的DFS问题的解决办法,即给定一个序列,枚举这个序列的所有子序列(可以不连续)。例如
对于序列{1,2,3}来说,它的子序列如下
{1},{2},{3}
{1,2},{1,3},{2,3}
{1,2,3}
这一类问题通常等价于枚举从N个整数中算则K个数的所有方案。
举一个题目例子,可以参照https://www.cnblogs.com/ManOK/p/12552540.html