前面我们会的dfs都是比较容易想象出搜索过程的,接下来我们看一些不那么容易想象的dfs过程,这个我们称之为抽象形式的dfs。
先看一个非常简单的问题:
给定n个整数,要求选出K个数,使得选出的K个数的和为sum。
其实,这就是一个组合问题。
我们可以依然借助深度优先搜索来解决这个问题。
我们在搜索过程中,用s来表示现在当前的数值总和,k来记录选择的数的个数,deep表示当前正在枚举的数是否选择。
我们可以画一个搜索树,搜索上每个结点都是一个状态,一个状态包含s和k,也就是一个状态对应当前的数值总和,一级选的数的个数。
在第一层dfs的时候,我们可以美剧是否选第一个数,如果选第一个数则让s+第一个数且k+1,dfs进入到下一层;否则dfs直接进入到下一层。当然,这里我们还需要借助全局变量,参数或修改数组中元素的值来识别当前的层数。
第二层,对第二个数做同样的处理……
dfs的过程中记录已经选取的数字的个数,如果已经取到了k个数,判断s是否等于sum。对于每一层,我们都有两个选择——选和不选。不同的选择,都会使搜索进入完全不用的分支继续搜索。
结合这棵树,我们来考虑一下动态规划和深度优先搜索的区别,动态规划是自底向上,深度优先搜索是自顶向下,也就是动态规划是先把后面画”……“的部分先计算,再网上算。搜索是从搜索树的根往下算,动态规划是从搜索树的叶子节点网上算,它快在同一个状态只算一次,当然如果想让搜索也有这个优点,可以用记忆化搜索。
dfs看起来是运行在图上的搜索算法,而现在介绍展示的dfs过程,我们并没有看到图的存在,这就是抽象形式的dfs的特点。
我们可以根据搜索状态建立一张抽象的图,图上的每个顶点就是一个状态,而图上的边就是状态之间转移的关系(进一步搜索或回溯)。虽然dfs是在这张抽象的图上进行的,但我们不必把这张图真正地建立出来。
我们可以认为,一次 dfs 实际上就是在搜索树上完成了一次深度优先搜索。而在上节中的搜索树里的每一个状态,记录了两个值——和值和个数。对于每个数,我们都有两个选择——选和不选。不同的选择,都会使得搜索进入完全不同的分支继续搜索。而每个状态对应的 子树,都是这个状态通过搜索可能达到的状态。
dfs需要传入3个状态参数,i表示现在正在选第几个数,cnt表示选取了几个数,一个s表示选取的数的和。
如果选取第i个数,那么cnt+1,s+a[i]
如果不选取第i个数,那么cnt和s都没有任何变化
当i==n时,我们已经对所有数都做出了选择,这时候我们就应该判断选出的数是否为k个,s判断和是否为sum。
#include <iostream>
using namespace std;
int n, k, sum, ans;
int a[40];
void dfs(int i,int cnt,int s){
if(i==n){
if(cnt