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

 

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM