☰
小学四年级也能看懂的DFS(10、DFS 综合练习第4课:魔法王国的终极挑战——简化版 N 皇后)
2026/10/23 2:58:22 网站建设 项目流程


DFS 综合练习第四课:魔法王国的终极挑战——简化版 N 皇后

这一课,我们把前面学过的 DFS 知识全部串起来!

前面三课,我们已经学会了:

  • 第一课:枚举所有可能,学会了选择。

  • 第二课:金币凑数、勇士爬台阶,学会了记录方案、检查结果。

  • 第三课:勇闯魔法地图,学会了检查条件、避免走进死胡同。

今天,我们要挑战一个经典的算法问题——N 皇后问题。

我们先不挑战 8×8 的大棋盘,而是从简单的 4×4 棋盘开始。只要弄懂这道题,将来再挑战更大的棋盘,就有了基础!


一、故事开始:四位皇后的魔法棋盘


魔法王国举办了一场棋盘挑战赛。

棋盘有 4 行、4 列,需要放置 4 位皇后,并且满足一个重要规则:

任何两位皇后都不能互相攻击。

在国际象棋中,皇后可以沿着横线、竖线和斜线移动。因此,两位皇后不能处于同一行、同一列或同一条斜线上。

我们把规则拆成三个孩子容易理解的要求:


规则一:不能同行

每一行只能放一位皇后。



规则二:不能同列

已经有皇后的列,不能再放另一位皇后。


规则三:不能同斜线

两位皇后如果沿着斜线相连,就会发生冲突。

我们的任务是:找出所有合法的摆放方案,并统计一共有多少种。

注意,这道题不是让我们随便摆四位皇后,而是要把所有符合规则的方案都找出来,一个也不能漏!


二、先不写代码:我们应该怎样思考?

还记得前面学过的“岔路口”吗?

每次来到一个岔路口,我们都尝试不同的选择。

这一次,我们不再决定“选还是不选”,而是:

来到第几行,就决定这一行的皇后放在哪一列。

比如,第一行有 4 个选择:


假设第一位皇后放在第 1 行、第 1 列。

接下来轮到第二行。第二行也有四个位置可以尝试,但有些位置已经不合法了。

  • 第 1 列:和第一位皇后同列,不行!

  • 第 2 列:和第一位皇后在斜线上,不行!

  • 第 3 列:没有同行、同列或斜线冲突,可以继续尝试。

  • 第 4 列:也没有冲突,可以继续尝试。

我们只沿着合法的选择继续往下探索。

如果后来发现无论怎样摆都无法完成任务,就退回上一行,换一个位置。

这就是 DFS 在这道题里的工作方式:

选择一个位置 → 检查是否合法 → 记录选择 → 继续下一行;如果走不通,就退回来换位置。


三、给电脑准备一本“皇后位置记录本”

如果我们每次都把棋盘画出来,电脑会不会很累?

其实,我们只需要用一个数组,就能记录每位皇后的位置。

int col[5];

这里使用下标1、2、3、4表示第几行,数组里的数字表示皇后所在的列。

例如:


记录本就是:第几行,对应第几列。

例如,col[2] = 4的意思就是:第二行的皇后放在第四列。

这样,我们就不必保存整个棋盘,只用几个数字就能记住摆放方案。


四、最重要的一步:怎样检查皇后是否合法?

假设我们正在给第row行摆放皇后。

前面几行的皇后已经摆好了,我们只需要检查它和前面每一位皇后有没有冲突。

检查一:是不是在同一列?

如果:

col[row] == col[i]

就说明当前皇后和第i行的皇后在同一列,发生冲突。

例如:

  • 第 1 行在第 2 列;

  • 第 3 行也想放在第 2 列。

两位皇后在同一列,所以不合法。


检查二:是不是在同一条斜线上?

这是本题最关键的知识点!

看下面这个例子:


我们用一个规律判断斜线:

两位皇后所在行的距离,等于它们所在列的距离,就在同一条斜线上。

例如:

  • 第一位皇后在第 1 行、第 2 列;

  • 第二位皇后在第 3 行、第 4 列。

行号相差 3-1 = 2,列号相差 4-2 = 2。两个距离相等,因此在同一条斜线上,不能这样摆放。


代码里可以这样判断:

if (row - i == col[row] - col[i] || row - i == col[i] - col[row]) { return false; }

为了让代码更简洁,我们也可以使用绝对值:

if (row - i == abs(col[row] - col[i])) { return false; }

这里的abs()是求绝对值。例如abs(-2)的结果是2。


五、把 DFS 的五个动作串起来

这道题里,DFS 不再是一个陌生的东西,而是我们熟悉的“岔路口探险”。


其中,剪枝可以理解为:发现这条路已经不可能成功,就不再浪费时间继续走。

例如,某位皇后已经和前面的皇后在同一列了,就没必要再摆后面的皇后了。我们直接放弃这个选择,换下一列。


六、完整 C++ 参考程序

接下来,我们把刚才的想法变成代码。建议同学们自己先猜一猜每个函数负责什么,再一起阅读。

#include <iostream> #include <cstdlib> using namespace std; const int N = 4; // col[row] 表示第 row 行的皇后所在的列 int col[N + 1]; int cnt = 0; // 检查当前行的皇后是否合法 bool check(int row) { for (int i = 1; i < row; i++) { // 检查是否在同一列 if (col[row] == col[i]) { return false; } // 检查是否在同一条斜线上 if (row - i == abs(col[row] - col[i])) { return false; } } return true; } // 从第 row 行开始摆放皇后 void dfs(int row) { // 四行都摆好了,找到一种完整方案 if (row == N + 1) { cnt++; cout << "方案 " << cnt << ":"; for (int i = 1; i <= N; i++) { cout << col[i] << " "; } cout << endl; // 打印棋盘 for (int i = 1; i <= N; i++) { for (int j = 1; j <= N; j++) { if (col[i] == j) { cout << "Q "; } else { cout << ". "; } } cout << endl; } cout << endl; return; } // 尝试把皇后放在当前行的每一列 for (int c = 1; c <= N; c++) { // 选择并记录位置 col[row] = c; // 检查合法,合法才继续搜索 if (check(row)) { dfs(row + 1); } // 恢复记录,尝试下一列 col[row] = 0; } } int main() { dfs(1); cout << "一共找到 " << cnt << " 种方案" << endl; return 0; }

七、逐步追踪:电脑是怎样找到第一种方案的?

先看程序最开始的调用:

dfs(1);

意思是:从第一行开始摆放皇后。

我们用一张表,跟着电脑走一遍。

当前行

尝试位置

检查结果

下一步

第 1 行

第 1 列

合法

进入第 2 行

第 2 行

第 1 列

同列冲突

剪枝

第 2 行

第 2 列

斜线冲突

剪枝

第 2 行

第 3 列

合法

进入第 3 行

第 3 行

逐列尝试

都无法完成

返回第 2 行

第 2 行

第 4 列

合法

重新探索第 3 行

这说明了一个重要道理:

第一行放在第 1 列,并不代表后面一定有解。

如果这一条路走不通,DFS 就会回到前面的岔路口,换一个选择。它不会因为前面已经花了时间,就死守错误的方案。

最终,程序会找到两种合法方案。



程序的主要输出如下:

方案 1:2 4 1 3 . Q . . . . . Q Q . . . . . Q . 方案 2:3 1 4 2 . . Q . Q . . . . . . Q . Q . . 一共找到 2 种方案

这里用Q表示皇后,用.表示空格。

注意:程序输出的是两种不同的合法摆法,不是只找到两位皇后。


八、最后把五个知识点讲透

1. 选择:for (int c = 1; c <= N; c++)

每一行都尝试第 1、2、3、4 列。这就是 DFS 的岔路口。

2. 记录:col[row] = c

把当前皇后的位置记下来,后面的皇后才能根据它检查冲突。

3. 检查:check(row)

检查同列和同斜线。每次只需要检查当前皇后与前面几行的皇后。

4. 剪枝:if (check(row))

只有合法的选择才能进入下一行。不合法的选择直接放弃,不继续搜索。

5. 恢复:col[row] = 0

当前选择探索完毕后,清除这一行的记录,接着尝试下一列。否则,之前的选择可能会影响后面的探索。


九、课后练习:试着自己当一回 DFS 探险家

练习一:理解数组

如果col[3] = 4,代表什么?

A、第三列放了第四位皇后

B、第三行的皇后放在第四列

C、第四行的皇后放在第三列

答案:B。

col[3] 表示第三行的皇后所在列。


练习二:理解剪枝

如果当前皇后与前面某位皇后在同一列,应该怎么办?

继续摆下一行

把已经摆好的皇后全部删除

放弃当前列,尝试下一列

答案:C。

当前选择不合法,就放弃它,尝试下一列。


练习三:理解恢复

为什么尝试完一个位置后,要执行col[row] = 0?

清除当前行的旧记录,方便尝试其他位置

让程序马上结束

把皇后移动到第 0 列

答案:A。

恢复记录,避免旧选择干扰后面的探索。


十、这一课的记忆口诀

每行选一列,先记再检查;

合法往下走,不合法就剪枝;

走完要恢复,找到答案就记录。

这就是 DFS 综合运用的完整过程。

从子集、排列、组合,到金币凑数、爬台阶,再到网格、迷宫和 N 皇后,我们一直在做同一件事:尝试选择,记录状态,检查条件,继续探索,走不通就返回。

同学们真正掌握了这个思路,以后遇到新的搜索题,就不只是背代码,而是能够自己分析:有哪些选择?怎样判断合法?什么时候继续?什么时候返回?这才是学习 DFS 最重要的收获。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询