深度优先搜索和宽度优先搜索
bfs和dfs都是遍历图的方法。
说明:
- bfs只能做权重为1或者相同的 “最短路”
- 空间上
- dfs 复杂度为最大深度 h。这也是stack最大容量。
- bfs 复杂度为 2^h。因为满二叉树 最下面一层为 2^(h-1)个节点。 这是个queue最大容量。
bfs——广度优先算法(暴搜)
- 适合解决最小步数问题。(bfs只能做权重为1或者相同的 “最短路”)
- 比dfs用时间更短。
- dfs是穷举。到一个点需要3步,那么bfs会在第三层就找到。
算法思路
-
一层层搜索
-
将一个点所有邻居加入队列中。
-
然后将列队中一个点,所有邻居加入队列。重复这个过程。
-
需要判重。加入过的。不再加入了。
模板
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
例题
迷宫
从左上到右下角。最少的步数。
想法
-
bfs + 记忆化
-
d[i][j]代表(0,0)到(i,j)最短距离
-
bfs第一次搜索到的右下角的点。d数组的值就是答案。
// AcWing 844. 走迷宫
// Created by majoe on 2020/5/20.
//https://www.acwing.com/activity/content/problem/content/907/1/
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n ,m;
int mi[N][N],d[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int bfs(){
queue<int> h;
queue<int> l;
//h代表行,l代表列。
h.push(1);
l.push(1);
memset(d,-1, sizeof(d));
d[1][1] = 0; // (1,1)到(1,1)距离为0
//不为空
while (!h.empty()){
int a = h.front(), b = l.front();
h.pop(); l.pop();
for (int i = 0; i < 4; ++i) {
int xx = a + dx[i];
int yy = b +dy[i];
if(xx>=1 && yy >=1 && xx <= n && yy <= m && mi[xx][yy] == 0 && d[xx][yy] == -1){
//d[xx][yy] = 1表示xx,yy没被访问过
h.push(xx);l.push(yy);
d[xx][yy] = d[a][b]+1;
}
}
}
return d[n][m];
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n ; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> mi[i][j];
}
}
int ans = bfs();
cout << ans;
return 0;
}
深度优先搜索 - dfs
特点
- 每次搜索都会到头
- 一般dfs遍历图。用邻接表比较好。时间复杂度低.
- 并且如果没有路的话,会回溯。也就是“回头”。
- 利用stack。进行回溯
- 回溯需要恢复现场
- 同时需要有一个vis数组,将用过的点。记录下
算法思路
void dfs(int step){//变量据题而命
if(判断边界){
...
}
//尝试每种可能;
for(int i=1;i<=n;i++){
//保证没有被访问过
if(vis[step]){
vis[step] = true;
dfs(step+1);//继续下一步
vis[step] = false; // 回溯
}
}
return;
}
一般dfs遍历图。用邻接表比较好。时间复杂度低.
模板
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
例题
数字的全排列
1-n数字的全排列。n=3时。
#include <iostream>
using namespace std;
const int N = 10;
int path[N];
bool st[N];
int n;
// 计算u->n的所经过的路径path
void dfs(int u)
{
// 边界条件
if (u == n)
{
for (int i = 0; i < n; i++) printf("%d ", path[i]);
puts("");
return;
}
else
{
for (int i = 1; i <= n; i++)
{
if (!st[i])
{
path[u] = i;
st[i] = true;
dfs(u+1); // 回溯之后,
st[i] = false;
}
}
}
}
int main()
{
scanf("%d", &n);
dfs(0);
return 0;
}
树的重心
题意
想法
- dfs
- 利用dfs遍历每一个节点。dfs()返回该节点下包含多少个节点。记做sum
- 当然,dfs(u)时,会切分子节点为若干个子树。记录子树的包含的最大节点数为 res
- 那么 max(res,n - sum) 的最小值就是 答案。
实现
//
//
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 +10; //无向图,容量应该是两倍
int n;//n个节点
int h[N],ne[N],e[N],idx;
int ans = 1e9;
int vis[N];
void add(int a, int b){
e[idx] = b; ne[idx] = h[a];h[a] = idx++;
}
//得到以U为顶点,包含的点的个数
int dfs(int u){
vis[u] = 1;//本次点只能遍历一次。所以不用回溯
int sum = 1,res=0;// res表示u节点某一节点包含节点的个数
//遍历u的直接边
for (int i = h[u]; i != -1 ; i = ne[i]) {
int j = e[i];//j为u指向的点
if (vis[j]) continue;
int s = dfs(j);
res = max(res,s);
sum += s; // sum表示 u 的包含的总结点树
}
res = max(res,n-sum);
ans = min(ans,res);
return sum;
}
int main(){
cin >> n;
memset(h,-1 , sizeof(h));
for (int i = 0; i < n - 1; ++i) {
//n-1条边
int a,b;
cin >> a >> b;
add(a,b);
add(b,a);//无向图
}
dfs(1);//遍历第一个点
cout << ans << endl;
return 0;
}