CSPJ 教学总结:深度优先搜索(DFS)
深度优先搜索(DFS)是学生学习算法的第一道门槛,因为它的主要形式是递归。而递归中需要将搜索的相关信息通过参数传递,这一点需要学生深刻理解 DFS。
模版
DFS 有比较标准的模版,如下所示:
1 | void dfs(int pt) // pt 表示层数 |
我们将运用该模版,完成后面的题目。
递归的深度
递归的时候,程序会占用栈空间来保存函数的环境变量。根据编译器以及编辑参数的不同,栈空间的大小也不同。通常情况下,竞赛中的编译器设定的栈空间为 8M 或者 16M。
假如,我们在一个递归函数中使用了 10 个 int 变量,那么每个递归函数就需要 4*10=40字节的栈空间。8M 一共可以支持 8*1000*1000/40=200000层调用。考虑到栈空间还需要保存当前函数执行的地址等变量,可供支持的调用层数会更小一点。
同学们也可以做如下的递归函数栈空间的测试:
1 | /** |
在我的本地,以上程序调用了约 13 万次后栈溢出。为了保险,我们在比赛中如果调用深度小于 1 万层,那应该是稳妥的;否则我们需要考虑是否用别的解法来解题。
教学和练习题目
| 题目名 | 说明 |
|---|---|
| P1036 选数 | NOIP 2002 普及组 |
| P1219 八皇后 Checker Challenge | USACO 1.5 |
| P1596 Lake Counting S | USACO10OCT |
| P2036 PERKET | COCI 2008/2009 #2 |
| P12139 黑白棋 | 蓝桥杯 2025 省 A,写起来较繁琐 |
| P1605 迷宫 | 标准的 DFS |
| P2404 自然数的拆分问题 | |
| P1019 单词接龙 | NOIP 2000 提高组 |
P7200
P10483
P1219 八皇后 Checker Challenge
这是八皇后的变种,N 皇后问题。可以作为基础练习。具体解法是:
- 我们用变量
v[15]表示每个皇后的列值。 - 对于新放入的皇后,我们依次检查它与前面的皇后是否在一条斜线上。检查方法是看其“横坐标差”与“纵坐标差”是否相同。检查函数如下:
1 | bool check(int pt) { |
完整的代码如下:
1 | /** |
P1036 选数
此题需要从小到大取数求和,然后再判断是否是素数。用递归的方式来进行枚举。
1 | /** |
P1596 Lake Counting S
此题既可以用 DFS,也可以用 BFS。考虑到 N 和 M 最大值为 100,所以递归的层次最多为 1 万层,所以我们可以试试 DFS。
以下是参考代码:
1 | /** |
P2036 PERKET
因为 N 最多为 10,每种食材可以选或者不选两种情况,所以最多情况数为 2^10=1024 种。搜索时间满足要求。
所以,此题用 DFS 可以非常方便解决。在搜索的时候,我们可以将食材的相关信息带到 DFS 函数的参数中,方便最后答案的求解。
1 | /** |
P12139 黑白棋
此题是搜索题,需要在中间尽可能检查状态来剪枝,以节省搜索次数。
题目有三类限制,分别可以用在不同的剪枝环节。
限制一:在每一行和每一列中,黑色棋子和白色棋子的数量必须相等(即为 3)。
- 我们可以对每一行记录黑子和白子的数量,如果某一行或某一列的一种颜色达到 3,后面则不能用这个颜色。
限制二:不能有超过两个相同颜色的棋子连续排列。
- 我们可以在当前落子的时候,检查它的左边和上面连续的几个格子,看是否有 3 个相同的子。
限制三:行列唯一性
- 可以放到最后检查。
另外,这个棋盘有几个位置已经设定了值,我们需要标记下来,搜索的时候跳过对这些位置的尝试,但需要在这些位置做合法性检查。
1 | /** |
P1605 迷宫
用 DFS 来枚举,但需要标记走过的路。
- 因为最多只有 5x5=25 个格子,所以递归的深度最大只有 25,不存在溢出情况。
- 因为有陷阱(不能走)和起点终点(不能重复走),所以我们假设平均每次有 2 条支路,
整个的最坏情况估计只有2^23=8388608次,所以也不会超时。
一些陷阱:
- 终点可能也有障碍物,这个时候始终就到不了。
- 起点在走之前需要标记,否则会重复走。
参考代码:
1 | #include <bits/stdc++.h> |
P2404 自然数的拆分问题
DFS,有两个技巧:
- 保证后面的数 >= 前面的数。
- 让每个数必须小于 n,这样不会出现
n=n这种等式。
1 | /** |







