文章目录
一、DFS
1. DFS 简介
2. DFS 的实现步骤
3. DFS 实际演示
二、DFS 例题——排列数字
具体实现
1. 样例演示
2. 实现思路
3. 代码注解
4. 实现代码
三、DFS 例题—— n-皇后问题(经典)
具体实现——按行进行枚举
1. 样例演示
2. 实现思路
3. 代码注解
4. 实现代码
具体实现——按格子进行枚举
1. 实现思路
2. 实现代码
一、DFS
- DFS 的关键点是递归和回溯。
1. DFS 简介
DFS(Depth First Search)是深度优先遍历,是图论当中一种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等。
其定义是,不断地沿着顶点的深度方向遍历。顶点的深度方向是指它的邻接点方向。
主要用于解决是否存在一个我们所需要的结果。因为 DFS 会首先把一种可能的情况尝试到底。才会回溯去尝试下一种情况,只要找到一种情况,就可以返回了。
DFS 问题一般分为两类:
(1) 定义的 DFS :对图的连通性进行测试,典型的问题:迷宫连通性测试、图的条件搜索等。
(2) 广义的 DFS–DFS 思路的应用: DFS 搜索顺序+规则问题、穷举结果寻求最优解/符合条件解等等,由于其穷举答案的本质,又被称为爆搜。
2. DFS 的实现步骤
- (1) 从顶点出发。
- (2) 访问顶点,也就是根节点。
- (3) 依次从顶点的未被访问的邻接点出发,进行深度优先遍历;直至和顶点有路径相通的顶点都被访问。
- (4) 若此时尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。
3. DFS 实际演示
(1) 首从根节点 1 开始遍历,它相邻的节点有 2,3,4,先遍历节点 2,再遍历 2 的子节点 5,然后再遍历 5 的子节点 9。
2) 上图中一条路已经走到底了( 9是叶子节点,再无可遍历的节点 ),此时就从 9 回退到上一个节点 5,看下节点 5 是否还有除 9 以外的节点,没有继续回退到 2,2 也没有除 5 以外的节点,回退到 1,1 有除 2 以外的节点 3,所以从节点 3 开始进行深度优先遍历,如下。
(3) 同理从 10 开始往上回溯到 6, 6 没有除 10 以外的子节点,再往上回溯,发现 3 有除 6 以外的子点 7,所以此时会遍历 7。
- (4) 从 7 往上回溯到 3, 1,发现 1 还有节点 4 未遍历,所以此时沿着 4, 8 进行遍历,这样就遍历完成了。
- (5) 完整的节点的遍历顺序如下(节点上的的蓝色数字代表遍历的顺序)。
二、DFS 例题——排列数字
题目描述
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1 ≤ n ≤ 7
输入样例
3
font size=5> 输出样例
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
具体实现
1. 样例演示
题目当中要求的是将数字 1∼n 排成一排,枚举每一种可能发送的情况。
最开始的时候,三个位置都是空的 _ _ _ 。
首先,填写第一个空位,第一个空位可以填 1 _ _ 。
填好第一个空位,填第二个空位,第二个空位可以填 2,填写后为:1 2 _ 。
填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为: 1 2 3 。
此时,空位填完,无法继续填数,因此,这是第一种方案,输出该方案,然后进行回溯。
退到了状态:1 2 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 3 ,没有其他数字可以填。
因此再往后退一步,退到了状态:1 _ _ 。第二个空位上除了填过的 2,还可以填 3 。第二个空位上填写 3,填写后为:1 3 _ 。
填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为:1 3 2 。
这时候,空位填完,无法继续填数,所以这是第二种方案,输出该方案,然后进行回溯。
然后往后退一步,退到了状态:1 3 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。
因此再往后退一步,退到了状态:1 _ _。第二个空位上除了填过的 2,3,没有其他数字可以填。
- 因此再往后退一步,退到了状态:_ _ _。第一个空位上除了填过的 1,还可以填 2 和 3 。
- 2 和 3 的填写方法跟 1 的填写方法相同,在此不做过多叙述,大家可以进行类比思考。
- 整体流程如下图所示。
2. 实现思路
- 详见样例演示。
3. 代码注解
st[N] 数组表示数字是否被用过,用 bool 类型对其进行表示,当其为 true 时表示被使用过,当其为 false 时,表示其没有被使用。
path[N] 数组保存排列。当排列的长度为 n 时,表明所有位置均已填充,为一种方案,对其进行输出。
dfs(i) 表示的含义是在 path[i] 处填写数字,然后递归的在下一个位置填写数字。
回溯是指第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。
在完成以某一个数字为第一位置所有情况的输出时,要进行回溯,然后要注意将当下情况恢复为原始情况,即 state[N] 变为 false ,尚未使用。在此处,path[i] 不需要进行恢复原样,因为 path[i] 会被覆盖。
4. 实现代码
#include <bits/stdc++.h> using namespace std; const int N = 10; int n; int path[N]; bool st[N]; void dfs(int u) { if (u == n) { for (int i = 0; i < n; i ++ ) { cout << path[i] << ' ' ; } cout << endl; return; } for (int i = 1; i <= n; i ++) { if (!st[i]) //判断这个数是否被使用过 { path[u] = i; st[i] = true; dfs(u + 1); //path[i]不需要进行恢复原样,因为path[i]都会被覆盖。 st[i] = false; } } } int main() { cin >> n; dfs(0); system("pause"); return 0; }
三、DFS 例题—— n-皇后问题(经典)
题目描述
n − 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n。
输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。
其中 .
表示某一个位置的方格状态为空,Q
表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1 ≤ n ≤ 9
输入样例
4
输出样例
.Q…
…Q
Q…
…Q.
…Q.
Q…
…Q
.Q…
具体实现——按行进行枚举
1. 样例演示
- 具体样例演示如下图:
2. 实现思路
- 首先,从第一行开始看,皇后可以放到哪一列。
- 然后每一行依次进行枚举。
- 注意:剪枝,就是提前判断,当前这个方案一定不合法时,直接将与这个方法相关的都完全排除掉。
3. 代码注解
- 因为 y-x 可能会是负数,但我们的数组下标不能是负数,所以,在此我们加上偏移量 n ,保证数组下标均为正数。
4. 实现代码
#include <bits/stdc++.h> using namespace std; const int N = 20; int n; char g[N][N]; bool col[N],dg[N],udg[N];//列,对角线,反对角线 void dfs(int u) { if (u == n) { for (int i = 0; i < n; i ++ ) { puts(g[i]); } puts(""); return; } for (int i = 0; i < n; i ++) { if (!col[i] && !dg[u + i] && !udg[n - u + i]) { g[u][i] = 'Q'; col[i] = dg[u + i] = udg[n - u + i] = true; dfs(u + 1); col[i] = dg[u + i] = udg[n - u + i] = false; g[u][i] = '.'; } } } int main() { cin >> n; for(int i = 0; i < n; i ++) { for(int j = 0;j < n; j ++) { g[i][j] = '.'; } } dfs(0); system("pause"); return 0; }
具体实现——按格子进行枚举
1. 实现思路
对每个格子进行枚举,每个格子都有放或不放两种情况,只要我们枚举到最后一个格子,就可以知道共有几种布局方案了。
2. 实现代码
#include <bits/stdc++.h> using namespace std; const int N = 20; int n; char g[N][N]; bool row[N], col[N], dg[N * 2], udg[N * 2]; void dfs(int x,int y,int s)//x,y 表示坐标,u表示当前有几个皇后 { if (s > n) { return; } if (y == n) { y = 0; x ++ ; } if (x == n) { if (s == n) { for (int i = 0; i < n; i ++ ) { puts(g[i]); } puts(""); } return; } //不放皇后 g[x][y] = '.'; dfs(x, y + 1, s); //放皇后 if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) { row[x] = col[y] = dg[x + y] = udg[x - y + n] = true; g[x][y] = 'Q'; dfs(x, y + 1, s + 1); g[x][y] = '.'; row[x] = col[y] = dg[x + y] = udg[x - y + n] = false; } } int main() { cin >> n; for(int i = 0; i < n; i ++) { for(int j = 0;j < n; j ++) { g[i][j] = '.'; } } dfs(0,0,0); system("pause"); return 0; }