第三章 搜索与图论(一)
DFS与BFS的区别与联系
- 都可以对整个问题空间进行遍历;
- 搜索的结构都像树一样;
- 但搜索的顺序是不同的;
- 深度优先搜索是尽可能往深里搜,当搜到叶子节点的时候回溯;
- D F S DFS DFS 就像一个非常执着的人,它会不断往深里搜,搜到头后回去的时候也还不是直接回到头,而是边回去边看能不能继续往前走,只有确定当前点所有路都走不了的时候,才会往回退一步;
下图中的树代表整个问题空间,节点上的数字代表 D F S DFS DFS 遍历的顺序:
DFS遍历顺序图示
- B F S BFS BFS 比较像眼观六路耳听八方(稳重)的人,它搜索时是一层一层地搜的,它可以同时看很多条路,它每次只会扩展一层,只有当前层扩展完后,才会去扩展下一层;
下图中的树代表整个问题空间,节点上的数字代表 B F S BFS BFS 遍历的顺序:
BFS遍历顺序图示
数据结构 | 空间 | ||
---|---|---|---|
D F S DFS DFS | s t a c k stack stack(栈) | O ( h ) O(h) O(h) , h h h表示树的深度 | 不具最短性 |
B F S BFS BFS | q u e u e queue queue(队列) | O ( 2 h ) O(2^h) O(2h), h h h表示树的层数 | “最短路” |
由于 B F S BFS BFS搜索时是一层一层往外扩展的,所以它第一次搜到的点一定是离它最近的点,因此它有一个最短路的概念.
例如下图情况,相同的点, D F S DFS DFS 搜索到时( 5 5 5 号点)就认为其距离根节点( 1 1 1 号点)为 4 4 4,而 B F S BFS BFS 搜索到时( 4 4 4号点)就认为其距离根节点( 1 1 1号点)为 2 2 2.
如果多加了一条边,则DFS与BFS的遍历顺序不同
Q: B F S BFS BFS 一般都是最短路吗?
A:不是,但当一个图的所有的权重都是 1 1 1 的时候, B F S BFS BFS搜到的一定是最短路,因为扩展时是先把所有距离其为 1 1 1 的点扩展进来,再把距离其为 2 2 2的点扩展进来,所以它是一层一层的,扩展的距离也是逐渐递增的,所以第一次扩展到的点一定是最近的点。凡是涉及到“最小步数”、“最短距离”、“最少操作几次“基本上都是 B F S BFS BFS;算法思路比较奇怪的或者对空间要求比较高的,一般都用 D F S DFS DFS.
Q: D F S DFS DFS 是递归吗?
A: D F S DFS DFS 其实就是递归,没必要把 D F S DFS DFS 和递归区分得太开.
Q:所以写 D F S DFS DFS 的时候要存为一棵树吗?
A:存的时候每次只会存当前这条路径,所以不需要把整棵树存下来,而且也不需要真的把栈写出来,系统会帮我们做回溯的,它有隐藏的栈来维护这个路径,不需要开额外的空间.
深度优先搜索 DFS
D F S DFS DFS 中有两个重要的概念:回溯和剪枝.
每一个 D F S DFS DFS 都对应一棵搜索树.
AcWing 842. 排列数字
给定一个整数 n n n,将数字 1 ∼ n 1\sim n 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n n n 。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1 ≤ n ≤ 7 1≤n≤7 1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
时/空限制: 1s / 64MB
来源: 模板题
算法标签:dfs
yxc’s Solution
-
如何用 D F S DFS DFS 来做这道题?
-
D F S DFS DFS 最重要的需要考虑的地方是 顺序;
-
D F S DFS DFS 对应的搜索顺序可以看作是一个 树 的形式:
-
假设一开始有 n n n 个空位;
-
顺序 是 从第一位开始填,从前往后一位一位地填。每次填的时候 填的数字不能和前面一样;
-
可以得到搜索树如下:
-
回溯 指的是 D F S DFS DFS 到叶子节点时,往回走的过程.
回溯中一定要注意一点:恢复现场.
#include<iostream>
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++) printf("%d ",path[i]);
puts("");
return;
}
for(int i=1;i<=n;i++)
if(!st[i])
{
path[u]=i;
st[i]=true;
dfs(u+1);
st[i]=false;//恢复现场
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
AcWing 843. n-皇后问题
n n n−皇后问题是指将 n n n 个皇后放在 n × n n \times n n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 n n n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n n n。
输出格式
每个解决方案占 n n n 行,每行输出一个长度为 n n n 的字符串,用来表示完整的棋盘状态。
其中 .
表示某一个位置的方格状态为空,Q
表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1 ≤ n ≤ 9 1≤n≤9 1≤n≤9
输入样例:
4
输出样例:
.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
时/空限制: 1s / 64MB
来源: 模板题,AcWing
算法标签:dfs
剪枝
yxc’s Solution1
- 顺序:从前往后看每一行,枚举每一行,看皇后放到哪一个列上.
- 剪枝:当前位置要枚举某一个数时,直接判断其有没有冲突,有冲突就没必要往下走了.
- 时间复杂度: O ( n × n ! ) O(n \times n!) O(n×n!).
这种方法保证了枚举时每行只有一个皇后.
剪枝 是指判断当前方案不合法后,就没有必要往下搜了,就相当于把子树剪掉,直接回溯.
对于 d g [ ] dg[] dg[],次对角线在直角坐标系上可以写成 y =