题解:AcWing 847 图中点的层次
【题目来源】
AcWing:847 图中点的层次 - AcWing题库
【题目描述】
给定一个\(n\)个点\(m\)条边的有向图,图中可能存在重边和自环。所有边的长度都是\(1\),点的编号为\(1\sim n\)。请你求出\(1\)号点到\(n\)号点的最短距离,如果从\(1\)号点无法走到\(n\)号点,输出\(-1\)。
【输入】
第一行包含两个整数\(n\)和\(m\)。
接下来\(m\)行,每行包含两个整数\(a\)和\(b\),表示存在一条从\(a\)走到\(b\)的长度为\(1\)的边。
【输出】
输出一个整数,表示\(1\)号点到\(n\)号点的最短距离。
【输入样例】
4 5
1 2
2 3
3 4
1 3
1 4
【输出样例】
1
【核心思想】
-
问题分析:给定 \(n\) 个点 \(m\) 条边的有向无权图,所有边长度为 \(1\),求从 \(1\) 号点到 \(n\) 号点的最短距离。这是一个经典的BFS求无权图最短路径问题,关键在于BFS按层扩展的特性保证第一次到达目标即为最短距离。
-
算法选择:
- BFS(广度优先搜索):按层遍历图,第 \(k\) 层节点距离起点为 \(k\)
- 邻接表(链式前向星):高效存储图结构,支持重边和自环
- 距离数组
d:记录从起点到每个节点的最短距离,\(-1\) 表示未访问 - 访问标记数组
st:避免重复访问节点
-
关键步骤:
- 建图:初始化邻接表,读入 \(m\) 条有向边
- BFS初始化:起点 \(1\) 入队,标记为已访问,
d[1] = 0 - BFS扩展:
- 取出队首节点 \(t\)
- 遍历 \(t\) 的所有邻接节点 \(j\)
- 若 \(j\) 未访问,更新
d[j] = d[t] + 1,标记为已访问,入队
- 返回答案:返回
d[n],若仍为 \(-1\) 表示不可达
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m)\),每个节点和边只被访问一次
- 空间复杂度:\(O(n + m)\),邻接表、距离数组和队列
-
BFS求最短路径的核心思想:
- 按层扩展:BFS从起点开始,先访问距离为 \(1\) 的节点,再访问距离为 \(2\) 的节点,以此类推
- 第一次访问即最短:由于边权相同,第一次访问某个节点时的距离即为最短距离
- 无权图最优:对于所有边权重相同的图,BFS是求最短路径的最优算法
- 适用于无权图或边权相同的图的最短路径问题
【解题思路】

【算法标签】
BFS-图
【代码详解】
// 使用STL队列
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 节点最大数量
int n, m; // n: 节点数量,m: 边数量
int h[N], e[N], ne[N], idx; // 邻接表存储图,h: 头节点数组,e: 边终点数组,ne: 下一条边数组,idx: 边计数器
int d[N]; // 记录从起点到每个节点的距离
bool st[N]; // 记录节点是否被访问过
queue<int> q; // BFS队列
// 添加一条有向边 a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 广度优先搜索,返回从节点1到节点n的最短距离
int bfs()
{
st[1] = true; // 标记起点为已访问
q.push(1); // 起点入队
memset(d, -1, sizeof(d)); // 初始化距离为-1(表示不可达)
d[1] = 0; // 起点距离为0
while (!q.empty())
{
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; // 标记为已访问
d[j] = d[t] + 1; // 更新距离
q.push(j); // 入队
}
}
}
return d[n]; // 返回到达节点n的距离
}
int main()
{
cin >> n >> m; // 读入节点数量和边数量
memset(h, -1, sizeof(h)); // 初始化邻接表头节点
// 读入边并建图
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs() << endl; // 输出从1到n的最短距离
return 0;
}
// 数据模拟队列
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 节点最大数量
int n, m; // n: 节点数量,m: 边数量
int h[N], e[N], ne[N], idx; // 邻接表存储图,h: 头节点数组,e: 边终点数组,ne: 下一条边数组,idx: 边计数器
int d[N], q[N]; // d: 记录从起点到每个节点的距离,q: 数组模拟队列
// 添加一条有向边 a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 广度优先搜索,返回从节点1到节点n的最短距离
int bfs()
{
int hh = 0, tt = 0; // 队列头尾指针
q[0] = 1; // 起点入队
memset(d, -1, sizeof(d)); // 初始化距离为-1(表示不可达)
d[1] = 0; // 起点距离为0
while (hh <= tt) // 队列不为空
{
int t = q[hh++]; // 取出队首节点
// 遍历当前节点的所有邻接节点
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i]; // 邻接节点编号
if (d[j] == -1) // 如果邻接节点未被访问
{
d[j] = d[t] + 1; // 更新距离
q[++tt] = j; // 入队
}
}
}
return d[n]; // 返回到达节点n的距离
}
int main()
{
cin >> n >> m; // 读入节点数量和边数量
memset(h, -1, sizeof(h)); // 初始化邻接表头节点
// 读入边并建图
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs() << endl; // 输出从1到n的最短距离
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 定义常量 N,用于数组大小
vector<int> h[N]; // 邻接表存储图
int d[N]; // 存储每个节点到起点的距离
queue<int> q; // BFS 使用的队列
int n, m; // n 是节点数量,m 是边的数量
// BFS 函数,从起点 u 开始遍历图
void bfs(int u) {
d[u] = 0; // 起点到自身的距离为 0
q.push(u); // 将起点加入队列
while (!q.empty()) { // 当队列不为空时
int nd = q.front(); // 取出队首节点
q.pop(); // 弹出队首节点
// 遍历当前节点的所有邻接节点
for (int i = 0; i < h[nd].size(); i++) {
int j = h[nd][i]; // 邻接节点 j
if (d[j] == -1) { // 如果节点 j 未被访问过
d[j] = d[nd] + 1; // 更新节点 j 的距离
q.push(j); // 将节点 j 加入队列
}
}
}
}
int main() {
memset(d, -1, sizeof(d)); // 初始化距离数组为 -1,表示未被访问
cin >> n >> m; // 读取节点数量 n 和边的数量 m
// 读取边的信息并构建图
for (int i = 1; i <= m; i++) {
int a, b;
cin >> a >> b; // 读取边的两个端点
h[a].push_back(b); // 添加边 a -> b
}
bfs(1); // 从节点 1 开始 BFS
cout << d[n]; // 输出节点 n 到节点 1 的最短距离
return 0;
}
【运行结果】
4 5
1 2
2 3
3 4
1 3
1 4
1
浙公网安备 33010602011771号