DFS and BFS
作者:互联网
DFS and BFS
一. DFS的基本概念
深度优先搜索(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当搜索遇到阻碍,如节点 的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点 的那条边的起始节点,换条路接着搜。
注意可以将dfs理解为栈,将各个子节点当作一个个的栈,然后将每个栈都遍历出来
问题示例
1.树或图的遍历问题
遍历无根树
#include<iostream>
#include<vector>
using namespace std;
const int N = 5e5+10;
int n,s;
vector<int> tre[N];//利用vector邻接链表来存树,表示与N相连接的点
void dfs(int u,int p)//u为子节点,p为父节点
{
cout << u << endl;
for(int i = 0;i < tre[u].size();i ++)//dev c++中不能使用for(int v;tre[u])来遍历vector容器(c++11特性)
{
if(tre[u][i]!=p) dfs(tre[u][i],u);
}
}
int main()
{
cin >> n >> s;
for(int i = 1,u,v;i < n;i ++)
{
cin >> u >> v;
tre[u].push_back(v);
tre[v].push_back(u);
}
dfs(s,0);//s为起点
return 0;
}
遍历图
#include<iostream>
#include<vector>
using namespace std;
const int N = 5e6+10;
int n,m,s;
int vis[N];
vector<int> gra[N];
void dfs(int s)
{
cout << s << endl;
vis[s] = 1;//标记已经搜过的节点
for(int i = 0;i < gra[u].size();i ++)//在c++11中可以使用for(int v,tra[u])来遍历vector容器
{
if(!vis[gra[u][i]]) dfs(gra[u][i]);//只搜没被标记的节点
}
}
int main()
{
cin >> n >> m >>s;
for(int i = 1,v,u;i <= n;i ++)
{
cin >> v >> u;
gra[u].push_back(v);
gra[v].push_back(u);
}
}
时间复杂度:O(n)
2.暴力搜索问题
题目链接:https://www.luogu.com.cn/problem/P1605
题目描述:给定一个 方格的迷宫,迷宫里有 处障碍,障碍处不可通过。给定起点坐标和终
点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右
四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
数据范围:1<= n,m <=5
样例输入:
2 2 1
1 1 2 2
1 2
样例输出:
1
思路;思路:暴力硬搜。我们用DFS的搜索策略,一条道走到黑,从起始点搜索出所有到终点的路径。在搜索的过程中遇到边界、障碍物和走过的点就返回,否则就接着搜下去,如果一个点无法继续走下去了,就回到上一个节点,同时把本节点的标记请0,换条路接着搜。
#include<iostream>
using namespace std;
int vis[10][10];//判断是否为障碍物,走过的点
int sx,sy,fx,fy,n,m,k;
int dx[] = {-1,1,0,0};//方向数组 对应上下左右
int dy[] = {0,0,-1,1};//方向数组 对应上下左右
int cnt;
void dfs(int x,int y)
{
if(x<1||y<1||x>n||y>m||vis[x][y]) return;//障碍物、被标记的点 ,边界都要返回
if(x==fx&&y==fy)
{
cnt++;
return;
}
vis[x][y] = 1;
for(int i = 0;i < 4;i ++) dfs(dx[i]+x,dy[i]+y);
vis[x][y] = 0;//无路可走了,往上回溯,这点我不走了,标记为回0
}
int main()
{
cin >> n >> m >> k;
cin >> sx >> sy >> fx >> fy;
for(int i = 0;i < k;i ++)
{
int x,y;
cin >> x >> y;
vis[x][y] = 1;
}
dfs(sx,sy);
cout << cnt << " ";
return 0;
}
3.联通块问题
题目链接:http://poj.org/problem?id=1562
题目大意:给你一个 的格子,格子中只包含 * 和 @ ,相邻的 @ 被视为同一块(周围八个方向的
格子都视为相邻,问你格子中一共有多少块 @。
数据范围:多组输入,1<= n,m <=100
1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0
样例输出:
0
1
2
2
思路:我们从每个未被访问过的 @ 出发,搜索它的八个方向上相邻的点,把 * 当成墙,遇到 * 就返回不搜了,遇到 @ 如果没被搜过就当成始点接着搜,搜完@后要标记,这样调用dfs的次数就是连通块的个数。因为每调用一次dfs就消除了一个连通块。
时间复杂度:O(n*m)
#include<iostream>
using namespace std;
char s[110][110];
int n,m;
int dx[]= {-1,-1,-1,0,0,1,1,1};//两个方向数组反别对应左上 上 右上 左 右 左下 下 右下
int dy[]= {-1,0,1,-1,1,-1,0,1};
int vis[110][110];//查询@是否被搜过
void dfs(int x,int y)
{
if(x<1||y<1||x>n||y>m||vis[x][y]||s[x][y]=='*') return ;//如果@已经被搜过了就返回
vis[x][y] = 1;
for(int i = 0;i < 8;i ++) dfs(dx[i]+x,dy[i]+y);
}
int main()
{
while(cin >> n >> m && n && m)
{
for(int i = 1;i <= n; i ++)
{
for(int j = 1;j <= m;j ++)
{
cin >> s[i][j];
vis[i][j] = 0;//多组输入要初始化
}
}
int ans = 0;
for(int i = 1;i <= n;i ++)
{
for(int j = 1;j <= m;j ++)
{
if(!vis[i][j]&&s[i][j]=='@')//@必须为没被遍历过
{
dfs(i,j);
ans++;
}
}
}
cout << ans << endl;
}
return 0;
}
其中
for(int i = 1;i <= n; i ++)
{
for(int j = 1;j <= m;j ++)
{
cin >> s[i][j];
vis[i][j] = 0;//多组输入要初始化
}
也可以写成
for(int i = 1;i <= n;i ++) cin >> s[i]+1;//s[i]是个二维字符数组,也可以当成一维字符串来处理 其中+1是为了保证下标从1开始,s[i]相当于指针
for(int i = 1;i <= n; i ++)
{
for(int j = 1;j <= m;j ++)
{
vis[i][j] = 0;
}
}
如果使用字符串的话下标只能从0开始,使用字符数组可以使下标从1开始,而且指针要加1即s[i]=1。
二.BFS基本概念
广度优先搜索(Breadth First Search,简称 BFS)从一个点出发,扩散到周围的点,按照层次优先的原则搜索完所有的点。形象点描述就像将一块石头扔到池塘里,石头所激发出的波纹传播到整个池塘的过程。
注意可以将其看作队列,遵守先进先出的原则
代码实现:
#include<iostream>
#include<queue>
using namespace std;
const int N = 5e6+10;
int n,m,s;
int vis[N];
vector<int> gra[N];
void bfs(int s)
{
queue<int> que;
que.push(s);
vis[s] = 1;
while(!que.empty())
{
int u = que.front();
que.pop();
cout << u << endl;
for(int i = 0;i < gra[s].size();i ++)
{
if(!vis[gra[s][i]]) continue;//入过队就不再搜了
que.push(gra[s][i]);
vis[gra[s][i]] = 1;//入队后打上标记
}
}
}
int main()
{
cin >> n >> m >> s;
for(int i = 0,u,v;i < n;i ++)
{
cin >> u >> v;
gra[u].push_back(v);
gra[v].push_back(u);
}
return 0;
}
问题示例
最短路
BFS的主要用途就是在边权为1的图上求最短路。当边权不为1时要用到floyd
例题:逃离迷宫
题目链接:https://ac.nowcoder.com/acm/contest/96/G?&headNav=www
题目大意:给你一个 的格子,格子中 ‘.’ 可以走 ,’#’ 不可以走,‘P’ 代表人物位置,‘K’ 代表钥匙(钥匙可以有多把),‘E’ 代表出口。每次可以花一单位时间向周围四个方向(上,下,左,右)走一格。逃出迷宫需要拿到钥匙到达出口,没有钥匙就无法通过出口,问你最少多久可以逃出迷宫,如果无法逃出则输出 “No solution”。
数据范围:多组数据 T 1 <= T <= 50 1 <= n,m <= 500
样例输入:
3
5 5
....P
##..E
K#...
##...
.....
5 5
P....
.....
..E..
.....
....K
5 5
P#..E
.#.#.
.#.#.
.#.#.
...#K
样例输出:
No solution
12
No solution
思路:由于bfs是广度搜索,本质是逐层便历,当某节点被第一次搜到时,这个距离便是它与起点的最短距离,对于此题我们首先要拿到钥匙,然后再从钥匙处走到出口,因此我们可以求出两段距离,一段是起点与钥匙的距离,另一段是钥匙与出口的距离(钥匙可以有多把),利用bfs可求出起点与所有点的最短距离,由于bfs是单源最短路,只能求一个点到多个点的最短距离,所以当求第二段距离时,可以求出口到各个钥匙的最短距离(逆向思维),可能会有多条道路,遍历出每把钥匙的位置,然后找出所有可能的道路,最后找出距离最小的道路,如果没有解,则输出No solution。
#include<iostream>
#include<queue>
using namespace std;
const int N = 150;
const int INF = 0x3f3f3f3f;
struct Node{
int x,y,w;//w为起点到该点的距离
};
char s[N][N];
int dis[2][N][N];//0与1分别表示两段不同的距离,0表示起点到钥匙,1表示钥匙到出口
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
int t,n,m;
queue<Node> que;
void bfs(int k,int x,int y)
{
for(int i = 1;i <= n;i ++)
{
for(int j = 1;j <= m;j ++) dis[k][i][j] = INF;//表示不能到达该点
}
que.push({x,y,0});
while(!que.empty())
{
Node t = que.front();
que.pop();
for(int i = 0;i < 4;i ++)
{
int x = t.x+dx[i],y = t.y+dy[i];
if(x<1||y<1||x>n||y>m||s[x][y]=='#'||s[x][y]=='E'||dis[k][x][y]!=INF) continue;//s[x][y]=='E',这个条件容易被忽略,在求第1段距离时不能穿过'E'
que.push({x,y,t.w+1});
dis[k][x][y] = t.w+1;
}
}
}
int main()
{
cin >> t;
while(t--)
{
cin >> n >> m;
for(int i = 1;i <= n;i ++) cin >> s[i]+1;
for(int i = 1;i <= n;i ++)
{
for(int j = 1;j <= m;j ++)
{
if(s[i][j]=='P') bfs(0,i,j);
if(s[i][j]=='E') bfs(1,i,j);
}
}
int ans = INF+INF;
for(int i = 1;i <= n;i ++)//枚举所有的钥匙点
{
for(int j = 1;j <= m;j ++)
{
if(s[i][j]=='K') ans = min(ans,dis[0][i][j]+dis[1][i][j]);//可能会有多个解,找出最小的
}
}
if(ans>=INF) cout << "No solution\n";
else cout << ans << endl;
}
}
标签:int,cin,dfs,BFS,vis,que,DFS,gra 来源: https://blog.csdn.net/yyds_hpu/article/details/123612050