边权为 1 的无向图最短路径求解:BFS 算法详解
在图论中,求解最短路径是一个基础且重要的问题。当图中所有边的权重都为 1 时,广度优先搜索(BFS)是解决这一问题的理想选择。本文将详细介绍如何使用 BFS 算法计算从顶点 1 到其他所有顶点的最短路径长度,并提供完整的实现代码。
问题分析
我们需要解决的问题是:
- 给定一个边权为 1 的无向图
- 计算从顶点 1 到其他所有顶点的最短路径长度
- 如果某个顶点无法到达,则输出 - 1
输入格式:
- 第一行包含两个整数 N 和 M,分别表示顶点数和边数
- 接下来 M 行,每行包含两个整数 x 和 y,表示顶点 x 和 y 之间有一条边
- 注意:图中可能存在自环(顶点到自身的边)和重边(同一对顶点间有多条边)
输出格式:
- 共 N 行,第 i 行输出从顶点 1 到顶点 i 的最短路径长度
算法选择
为什么选择 BFS 算法?
- BFS(广度优先搜索)的特性是按层次遍历图,先访问距离起点近的顶点,再访问距离远的顶点
- 对于边权为 1 的图,BFS 第一次到达某个顶点时所经过的路径就是最短路径
- BFS 的时间复杂度为 O (N+M),空间复杂度为 O (N+M),能够高效处理大规模图(如本题中 N 和 M 可达 10^6)
实现步骤详解
1. 头文件与命名空间
#include <iostream> // 用于输入输出操作
#include <vector> // 用于存储邻接表
#include <queue> // 用于BFS的队列实现
using namespace std; // 避免每次使用标准库都添加std::前缀
这些是 C++ 中处理图和 BFS 所需的基本库:
iostream:提供输入输出功能vector:动态数组,适合存储邻接表queue:队列数据结构,是 BFS 的核心
2. 输入输出优化
int main() {
// 关闭同步,加速输入输出
ios::sync_with_stdio(false);
cin.tie(nullptr);
这两行代码是处理大规模输入输出时的关键优化:
ios::sync_with_stdio(false):解除 C++ 标准输入输出与 C 标准输入输出的同步cin.tie(nullptr):解除 cin 与 cout 的绑定- 这两项优化可以显著提高输入输出速度,对于 10^6 级别的数据处理尤为重要
3. 读取图的基本信息
int N, M;
cin >> N >> M; // 读取顶点数和边数
- N:表示图中顶点的数量(顶点编号从 1 到 N)
- M:表示图中边的数量
4. 构建邻接表
// 构建邻接表表示图,顶点编号从1开始,所以大小为N+1
vector<vector<int>> adj(N + 1);
for (int i = 0; i < M; ++i) {
int x, y;
cin >> x >> y;
adj[x].push_back(y); // x连接到y
adj[y].push_back(x); // 无向图,y也连接到x
}
邻接表是表示图的高效方式:
adj是一个 vector 的 vector,adj[u]存储了所有与 u 直接相连的顶点- 由于是无向图,每条边 (x,y) 需要双向添加到邻接表中
- 对于自环(x=y),代码会自动处理,不影响最终结果
- 对于重边,虽然会被多次添加到邻接表,但 BFS 只会处理一次,不影响结果正确性
5. 初始化距离数组和队列
// 距离数组,-1表示不可达
vector<int> dist(N + 1, -1);
queue<int> q; // BFS使用的队列
// 起点是1,距离为0
dist[1] = 0;
q.push(1); // 将起点加入队列
dist数组:dist[i]表示从顶点 1 到顶点 i 的最短距离- 初始值设为 - 1,表示所有顶点都不可达
- 起点 1 的距离设为 0
q队列:用于 BFS 过程中存储待处理的顶点,首先将起点 1 加入队列
6. BFS 核心算法
// BFS计算最短路径
while (!q.empty()) { // 队列不为空时继续
int u = q.front(); // 取出队首顶点
q.pop();
// 遍历u的所有邻居
for (int v : adj[u]) {
// 如果v未被访问过(距离为-1)
if (dist[v] == -1) {
dist[v] = dist[u] + 1; // 距离是u的距离+1(边权为1)
q.push(v); // 将v加入队列,继续探索
}
}
}
BFS 算法的核心逻辑:
- 当队列不为空时,取出队首顶点 u
- 遍历 u 的所有邻居 v
- 对于尚未访问的邻居 v(
dist[v] == -1):- 设置 v 的距离为 u 的距离 + 1(因为边权为 1)
- 将 v 加入队列,以便后续处理其邻居
- 重复以上步骤直到队列为空
BFS 的特性保证了每个顶点只被访问一次,且第一次被访问时的距离就是最短距离。
7. 输出结果
// 输出从顶点1到每个顶点的最短距离
for (int i = 1; i <= N; ++i) {
cout << dist[i] << '\n';
}
return 0;
}
按顶点编号从 1 到 N 的顺序,输出每个顶点到起点 1 的最短距离。对于无法到达的顶点,会输出初始值 - 1。
完整代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int main() {
// 关闭同步,加速输入输出
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M;
cin >> N >> M; // 读取顶点数和边数
// 构建邻接表表示图,顶点编号从1开始,所以大小为N+1
vector<vector<int>> adj(N + 1);
for (int i = 0; i < M; ++i) {
int x, y;
cin >> x >> y;
adj[x].push_back(y); // x连接到y
adj[y].push_back(x); // 无向图,y也连接到x
}
// 距离数组,-1表示不可达
vector<int> dist(N + 1, -1);
queue<int> q; // BFS使用的队列
// 起点是1,距离为0
dist[1] = 0;
q.push(1); // 将起点加入队列
// BFS计算最短路径
while (!q.empty()) { // 队列不为空时继续
int u = q.front(); // 取出队首顶点
q.pop();
// 遍历u的所有邻居
for (int v : adj[u]) {
// 如果v未被访问过(距离为-1)
if (dist[v] == -1) {
dist[v] = dist[u] + 1; // 距离是u的距离+1(边权为1)
q.push(v); // 将v加入队列,继续探索
}
}
}
// 输出从顶点1到每个顶点的最短距离
for (int i = 1; i <= N; ++i) {
cout << dist[i] << '\n';
}
return 0;
}
示例解析
以题目中给出的示例输入为例:
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
这个图包含 5 个顶点和 7 条边,其结构如下:
- 顶点 1 与顶点 2、3 相连
- 顶点 2 与顶点 1、4、3 相连
- 顶点 3 与顶点 1、4、2 相连
- 顶点 4 与顶点 2、3、5 相连(有两条边连接 4 和 5)
- 顶点 5 与顶点 4 相连
BFS 的执行过程:
- 初始队列:[1],dist [1]=0
- 处理顶点 1:将 2 和 3 加入队列,dist [2]=1,dist [3]=1,队列变为 [2,3]
- 处理顶点 2:将 4 加入队列,dist [4]=2,队列变为 [3,4]
- 处理顶点 3:4 已被访问,队列变为 [4]
- 处理顶点 4:将 5 加入队列,dist [5]=3,队列变为 [5]
- 处理顶点 5:没有未访问的邻居,队列变为空
最终输出结果:
0 // 顶点1到自身的距离
1 // 顶点1到顶点2的距离
1 // 顶点1到顶点3的距离
2 // 顶点1到顶点4的距离
3 // 顶点1到顶点5的距离
复杂度分析
-
时间复杂度:O (N + M)
- 每个顶点最多入队一次,出队一次:O (N)
- 每条边最多被处理两次(无向图):O (M)
-
空间复杂度:O (N + M)
- 邻接表存储:O (M)
- 距离数组:O (N)
- 队列存储:最坏情况下 O (N)
这种复杂度使得算法能够高效处理 10^6 规模的图数据。
总结
本文详细介绍了使用 BFS 算法求解边权为 1 的无向图最短路径问题:
- BFS 算法特别适合处理边权为 1 的图的最短路径问题
- 邻接表是表示图的高效数据结构
- 输入输出优化对于处理大规模数据至关重要
- 算法能够自动处理自环和重边等特殊情况
通过本文的学习,你应该能够理解 BFS 算法的工作原理及其在最短路径问题中的应用,并能够独立实现相应的代码解决类似问题。
5981

被折叠的 条评论
为什么被折叠?



