DFS算法的本质

Posted 桃花涣小鱼干

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DFS算法的本质相关的知识,希望对你有一定的参考价值。

DFS算法的本质

引言

DFS 俗称深搜,是一种常见的算法模型

我们通过借助函数递归递归停止条件的运用实现对数据的高级枚举

对于DFS算法而言,最重要的是如何去枚举数据,即如何去搜索?

这是在运用DFS之前我们就应该思考的问题,只有对其思考清楚,才不妨碍我们下一步去运用代码实现DFS!

OK! 让我们开始吧!


何为搜索?

让我们先忘记你学过DFS算法这件事,抛开脑子中具体的代码结构,单纯地以一个自然人地角度去思考,什么是搜索?

单独思考一个词汇可能我们不会获得很多,下面我们借助具体例子来说明:

假如我说——我们去搜索一间房子,你会想到什么?

寻找?探索?还是在房子里面转悠?

可能这么说会引起歧义,这里我们再具体一点

换成——我们去搜索这间房子,看看有没有宝藏藏在里面!

上面两句话地区别在于,一个无目的,一个有目的

一个只是再房间里面转悠,一个在转悠地同时还要看看房间里面有没有宝藏

其实上面这两句话对应了DFS算法的两种基本类型

无目的的深度搜索——裸的DFS
有目的的深度搜索——一般的DFS

(1)无目的DFS就像你去亲戚家做客,主人带你在他家闲逛了解他家的布局

(2)有目的DFS就像土匪掠夺财产时在你家到处探寻,直到找到你的私人小金库!

但作为 算法 而言我们不能到处瞎转悠或到处探寻

有经验的主人和土匪往往也不会那莫做!

我们需要一种高效的搜索方案,它能让我们在最短时间内完成搜索任务

接下来就是如何去搜索?


如何搜索?

DFS的关键要领在于——当下这一步干什么,然后下一步干什么。

这是所有DFS搜索🔍算法的核心,我们只要知道当下和下一步的动作,就能通过递归连贯出整个搜索动作。

让我们来举个例子
DFS - 选数
题目大意:求从N个数里面选出K个数出来并且使选出的数的和为素数的的可能性有几种?
假如:N5,K3
5个数:1 2 3 4 5
我们要做的是挑出3个数出来然后计算求和并判断和是否为素数。

你可能已经想到怎么去选了,一般的选法如下:

1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4

我们要做的就是每一次选出一个数,首先仔细观察上述选数过程
当我们去选1 2 3时我们发现这是依次递增的,我们不免想到用For循环去实现这个过程
可能你会这么写:

dfs(int i,int k,int n)//选入的数为i,选了k个数,要选n个数
{
	if(k==n)
	//略
	for(int i=1;i<=n;i++)
	dfs(i+1,k+1,n)
	//略
}

但问题来了我们如何在1 2 3选完之后蹦到1 2 4呢?

难不成我们需要再从头选一遍,标记3已经被选过了,直接越过它去选4

仔细思考你会发现这是一个无比繁琐的工作,你需要灵活的标记策略来应对不同房的选数情况

我们想要的是不再从头去选,而是在选完1 2的基础上再去直接选4

所以我们需要一种方法来保留选完1 2时的状态

可能DFS算法尚在研究时人们就发现了,他们需要保留一些状态,再去确定下一步的状态

巧妙的是函数递归在设计时就拥有保留中断线程的能力,在选数时我们可以完全用函数递归来写

这样写的好处在于,每一层函数选着一个数,选着完后就像线路一样被保存下来

最简单的我们选完11的状态就被保存下来了,我们要在1后面选数时,只需要return返回到选完1时的状态即可,这样我们可以顺利地选出

1 2
1 3
1 4

前两个数选完后,也就保存了选完两个数的状态

其实我们可以用结点去理解这个过程

最开始我们选择了1这个结点,然后我们就可以连接2/3/4这三个结点

假如我们连接了1和2结点就形成了1-2线路,当我么们再往下DFS选数时,1-2这条线路会被保存下来,通过选数和不断return返回到1-2这条线路的末尾,我们选出了:

1 2 3
1 2 4
1 2 5

核心代码如下:

void dfs(int x,int m,int sum,int k,int n)
{
	if (m == k)
	{
		if (isPrime(sum))
			ans++;
		return;
	}
	for (int i = x; i < n; i++)
		dfs(i+1,m + 1, sum + *(p + i), k, n);//i+1而非x+1
	return;
}

部分过程如下:
1 //选1结点
1-2 //保留1结点选2结点
1-2-3 //保留1-2线路选3结点
1-2 //返回到1-2线路的末尾
1-2-4 //在已保留1-2线路后选4结点
1-2 //返回到1-2线路的末尾
1-2-5 //以此类推!!!
1-2
1
1-3
1-3-4
1-3
1-3-5
1-3
1

这也是为什么你所见到的DFS算法都拥有递归调用的原因

DFS算法本就是依托于函数递归而设计的一种高级枚举算法


感谢阅读!

END

以上是关于DFS算法的本质的主要内容,如果未能解决你的问题,请参考以下文章

小白也能看懂的 DFS 算法本质详解

DFS算法的本质

(王道408考研数据结构)第六章图-第三节:图的遍历(DFS和BFS)

七十九深度和广度优先搜索算法

DFS算法(——模板习题与总结)

DFS飞行员兄弟