文章目录
- 一、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;
}