dfs刷题模板总结

0.前言

本文主要讲解平常刷题中常用到的dfs,并总结出了基本套路模板。

1.什么时候用dfs?(该用的时候用), 屁话

dfs是经常使用的暴力解法。常常,其优化版本要么是记忆化搜索,要么就是dp。但是因为dfs很好想,而且很好实现(只要递归即可),所以dfs常常出现在题解中。

2.如何用dfs?

  • dfs出现的地方,常常对应的是多个选择的地方,选或者不选;放或者不放;做或者不做…等等。再上一个选择的基础上,又迎来下面的一波选择。
  • 为了避免重复选择,通常我们会用vis[]数组标记当前这轮深搜时的选择情况,但是需要注意的是,在当前这轮结束的时候,通常(几乎就是所有的情况)就需要将
    这个vis数组清除做的标记,因为,别的情况可能会因为这个标记而做出错误选择。即,应该保证:
    某轮做的标记,在该轮结束时,应该予以清除。需要做出选择的时候,通常就是调用dfs的时候。

3.什么时候dfs在for循环里,什么时候又不在?判断的依据是什么?

for循环也是一种选择的体现,比如说一个顶点有很多条分支,这时就可以使用for依次遍历各个分支,然后把这个分支所在的节点作为参数传递到dfs中,进行下一次的深搜。

  • 在for循环中的dfs通常用于图的遍历。
  • 直接dfs的情况通常在于非图的遍历。

4.TLE,TLE,TLE…

如果在使用dfs时,出现TLE,不要害怕,出现这种情况只有如下几种情况:

  • 1.是否在一轮dfs中,标记数组由1变为了0,导致无限的循环出现超时。
  • 是否是因为剪枝不够(同时也是数据太强),导致出现计算的时间太久。

4.常见模板

4.1 伪代码

这里给出常用的模板,不过是伪代码形式。

void dfs(){
	/*1.递归边界
		常见的递归边界有: 
		01.坐标越界
		02.搜索到了极限
		03.出现重复搜索,或者不是最优的搜索 
	*/
	
	/*
	2.递归调用的主体,这里一般会用for循环,或者是直接调用两个dfs即可。 
	*/  
}

如果还想使dfs更快,则应该使用记忆化搜索+剪枝操作。这样几乎可以和dp得到相同的时间复杂度。

4.2 优化方法

如上所述。常见的优化方法有:记忆化搜索+剪枝。下面我分别来谈。

  • 记忆化搜索就是:将一次搜索的结果放在一个容器中存储下来。等到下次再搜索到这个点时,就应该直接返回之前搜索的值,而不是再往下搜索了。
  • 剪枝
    常见的剪枝操作,主要分为两类:
    一类是针对题意做的剪枝,就是题目潜藏的存在一种更优解的顺序(这可以参考络谷 P3956 棋盘 这道题。我们怎么走棋盘是有顺序可言的。走了一条,就不用走另一条了。这样就可以成倍的减少dfs)。
    第二类就是针对结果的剪枝,如果已经得出了最优解,但是当前深搜的解已经不比这个解更好,那么就应该主动放弃搜索。

5.例题

  • 走迷宫
    这种题目,其方向只有上下左右四种,或者是上下左右,左上,右上,右下,左下八个方向,则需要使用方向数组,在for循环中进行dfs(代表往不同的方向行走)即可。