广度优先搜索算法刷题笔记【蓝桥杯】
理论
BFS算法一般用于搜索最短路径问题,即在图结构中从一个顶点出发找到到另一个顶点的最短路径。BFS算法的设计步骤如下:
-
定义一个队列,将起点加入队列。
-
标记起点为已访问。
-
从队列中取出一个顶点a,遍历其所有邻接顶点,选择未标记的顶点b,将b加入队列中。并标记b为已访问。
-
重复执行第3步,直到找到目标顶点或者队列为空。
-
如果队列为空,说明无法找到目标顶点。
BFS算法可以用于许多问题,如图的连通性、迷宫问题、单源最短路径问题等。
需要注意的是,BFS算法对于图的遍历是逐层进行,因此在最短路径问题中可以保证找到的路径一定是最短的。
在实现时,需要使用数据结构来存储图结构,通常使用邻接矩阵或邻接表。同时,需要使用一个数据结构来存储已经访问过的顶点,避免重复访问。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int MAXN = 100;
int n, m;
bool visited[MAXN];
vector<int> graph[MAXN];
void bfs(int start) {queue<int> q;q.push(start);visited[start] = true;while (!q.empty()) {int cur = q.front();q.pop();cout << cur << " ";for (int i = 0; i < graph[cur].size(); i++) {int next = graph[cur][i];if (!visited[next]) {visited[next] = true;q.push(next);}}}
}int main() {cin >> n >> m;for (int i = 1; i <= m; i++) {int u, v;cin >> u >> v;graph[u].push_back(v);graph[v].push_back(u);}bfs(1);
}
BFS (Breadth-First Search) 和 Dijkstra 算法都是求解图中单源最短路径的算法,它们之间的区别主要有以下几个方面:
-
适用范围不同:
BFS 算法主要适用于没有权重或者权重都相同的图,而 Dijkstra 算法适用于带有正权重的有向图或者无向图。 -
需要维护的数据结构不同:
在 BFS 算法中,需要维护一个队列用来存储待访问的节点,而 Dijkstra 算法则需要使用最小堆等结构来快速获取距离源节点最近的节点。 -
路径距离计算方式不同:
在 BFS 算法中,当前节点的路径长度都是相等的,即每次访问相邻节点的距离均为1,而在 Dijkstra 算法中,则需要在每次访问相邻节点时根据其权重计算出与源节点的距离,然后更新距离值。 -
完成条件不同:
BFS 算法是按层逐步遍历,直到找到目标节点为止,因此会把全部节点都遍历一遍;而 Dijkstra 算法则是根据当前节点与源节点的距离来选择最短路径,如果到目标节点的最短路径已经找到,就可以停止搜索。
因此,BFS 算法和 Dijkstra 算法根据不同的图结构和应用场景选择合适的算法来求解单源最短路径问题。
虽然 Dijkstra 算法可以适用于没有权重或者权重都相同的图,但在这种情况下,BFS 算法的效率更高。当图中所有的边权重都相同时,Dijkstra 算法在每次迭代时浪费很多时间去更新节点的距离和查找最短的距离,而 BFS 不需要这些操作,只需要按层遍历即可。因此,对于没有权重或权重相同的图,应该采用BFS算法以获得更好的效率。
练习
最短路计数
给出一个 NNN 个顶点 MMM 条边的无向无权图,顶点编号为 1∼N1\\sim N1∼N。问从顶点 111 开始,到其他每个点的最短路有几条。
输入格式
第一行包含 222 个正整数 N,MN,MN,M,为图的顶点数与边数。
接下来 MMM 行,每行 222 个正整数 x,yx,yx,y,表示有一条由顶点 xxx 连向顶点 yyy 的边,请注意可能有自环与重边。
输出格式
共 NNN 行,每行一个非负整数,第 iii 行输出从顶点 111 到顶点 iii 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 ansmod100003ans \\bmod 100003ansmod100003 后的结果即可。如果无法到达顶点 iii 则输出 000。
样例输入 #1
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
样例输出 #1
1
1
1
2
4
提示
111 到 555 的最短路有 444 条,分别为 222 条 1→2→4→51\\to 2\\to 4\\to 51→2→4→5 和 222 条 1→3→4→51\\to 3\\to 4\\to 51→3→4→5(由于 4→54\\to 54→5 的边有 222 条)。
对于 20%20\\%20% 的数据,1≤N≤1001\\le N \\le 1001≤N≤100;
对于 60%60\\%60% 的数据,1≤N≤1031\\le N \\le 10^31≤N≤103;
对于 100%100\\%100% 的数据,1≤N≤1061\\le N\\le10^61≤N≤106,1≤M≤2×1061\\le M\\le 2\\times 10^61≤M≤2×106。
思路
- 题目:无向无权图、最短路,可以说明用bfsbfsbfs算法
- 这道题主要是帮助理解bfsbfsbfs算法,具体看代码注释
题解
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+1,INF=0x7f7f7f7f,MOD=100003;
vector<int>G[maxn];
int dep[maxn],cnt[maxn];
bool vis[maxn];
int main(){int N,M;scanf("%d%d",&N,&M);for(int i=1;i<=M;i++){int x,y;scanf("%d%d",&x,&y);G[x].push_back(y); //邻接表G[y].push_back(x);}queue<int>Q;dep[1]=0;vis[1]=1;Q.push(1);cnt[1]=1; //初始化while(!Q.empty()){int x=Q.front();Q.pop(); //模板for(int i=0;i<G[x].size();i++){int t=G[x][i];if(!vis[t]){vis[t]=1;dep[t]=dep[x]+1;Q.push(t);}if(dep[t]==dep[x]+1) cnt[t]=(cnt[t]+cnt[x])%MOD; //t代表走到的点,x代表出发的点。被走到的点还要加上出发的点的数目}}for(int i=1;i<=N;i++) printf("%d\\n",cnt[i]);
}
[蓝桥杯 2018 国 C] 迷宫与陷阱
小明在玩一款迷宫游戏,在游戏中他要控制自己的角色离开一间由 N×NN \\times NN×N 个格子组成的二维迷宫。
小明的起始位置在左上角,他需要到达右下角的格子才能离开迷宫。
每一步,他可以移动到上下左右相邻的格子中(前提是目标格子可以经过)。
迷宫中有些格子小明可以经过,我们用 .
表示;
有些格子是墙壁,小明不能经过,我们用 #
表示。
此外,有些格子上有陷阱,我们用 X
表示。除非小明处于无敌状态,否则不能经过。
有些格子上有无敌道具,我们用 %
表示。
当小明第一次到达该格子时,自动获得无敌状态,无敌状态会持续 KKK 步。
之后如果再次到达该格子不会获得无敌状态了。
处于无敌状态时,可以经过有陷阱的格子,但是不会拆除 / 毁坏陷阱,即陷阱仍会阻止没有无敌状态的角色经过。
给定迷宫,请你计算小明最少经过几步可以离开迷宫。
输入格式
第一行包含两个整数 NNN 和 KKK。(1≤N≤1000,1≤K≤10)(1 \\le N \\le 1000,1 \\le K \\le 10)(1≤N≤1000,1≤K≤10)。
以下 NNN 行包含一个 N×NN\\times NN×N 的矩阵。
矩阵保证左上角和右下角是 .
。
输出格式
一个整数表示答案。如果小明不能离开迷宫,输出 −1-1−1。
样例输入 #1
5 3
...XX
##%#.
...#.
..
.....
样例输出 #1
10
样例输入 #2
5 1
...XX
##%#.
...#.
..
.....
样例输出 #2
12
提示
时限 3 秒, 256M。蓝桥杯 2018 年第九届国赛
思路
- 无权、最短路表明应该首先思考bfsbfsbfs
- 做好无敌状态的处理即可
题解
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int n, k;
char g[N][N];
int vis[N][N]; // 存储每个格子是否被访问过以及无敌状态剩余步数
struct node{int x, y, step, magic;
};
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int main(){cin >> n >> k;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )cin >> g[i][j];memset(vis, -1, sizeof vis);queue<node> q;vis[1][1] = 0; // 将起点的访问状态设置为0q.push({1, 1, 0, 0}); // 将起点入队,并设置其到达该点的步数为0、当前不处于无敌状态while (q.size()){node t = q.front(); // 取出队头节点q.pop();if (t.x == n && t.y == n){ // 如果当前节点为终点,则输出最短路长度并结束程序cout << t.step;return 0;}for (int i = 0; i < 4; i ++ ){int tx = t.x + dx[i];int ty = t.y + dy[i];if (g[tx][ty] == 'X' && t.magic == 0) // 如果下一步位置是陷阱且当前不处于无敌状态,则跳过该节点continue;int magic = max(0, t.magic - 1); // 计算当前无敌状态剩余步数if (g[tx][ty] == '%') // 如果下一步位置有道具,更新无敌状态剩余步数magic = k;if (tx >= 1 && tx <= n && ty >= 1 && ty <= n && vis[tx][ty] < magic && g[tx][ty] != '#'){ // 如果下一步位置是合法的可到达位置vis[tx][ty] = magic; // 更新访问状态和无敌状态剩余步数q.push({tx, ty, t.step + 1, magic}); // 将下一步位置入队,并更新到达该点的步数和无敌状态剩余步数}}}cout << -1;
}