深度优先搜索(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

 

 

 

posted @ 2020-03-23 15:41  focusDing  阅读(1061)  评论(0编辑  收藏  举报