43.Acwing基础课第847题-简单-图中点的层次
43.Acwing基础课第847题-简单-图中点的层次
题目描述
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。
所有边的长度都是 1,点的编号为 1~n。
请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 a和 b,表示存在一条从 a走到 b的长度为 1的边。
输出格式
输出一个整数,表示 1号点到 n号点的最短距离。
数据范围
1≤n,m≤105
输入样例:
4 5
1 2
2
3 4
1 3
1 4
输出样例:
1

代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx;//邻接表结构
bool st[N];//记录每个节点是否被访问过
int dist[N];//存储距离
int n, m;
//构建结点a到b的边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs()
{
memset(dist, -1, sizeof dist);//初始化距离为无穷大
dist[1] = 0;//从1号结点开始,距离为0
queue<int> q;//队列
q.push(1);//1号结点入队
st[1] = true;//1号结点已经被访问
while(q.size())//队列非空就继续往后搜索
{
int t = q.front();//获取队头元素
q.pop();//出队
for(int i = h[t]; i != -1; i = ne[i])//遍历所有t节点能到的点,i为节点索引
{
int j = e[i];//通过索引i得到结点编号
if(st[j] == false)//结点未被访问
{
dist[j] = dist[t] + 1;///距离为t号节点的距离+1
q.push(j);//结点入队
st[j] = true;//入队后标记,已经遍历过了
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);//初始化邻接表 所有节点没有后继,后继都是-1
for(int i = 0; i < m; i++)//读入所有边
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);//构建结点a到b的边
}
bfs();//广度优先遍历
cout << (dist[n] == -1 ? -1 : dist[n]) << endl;//如果到n号节点的距离不是无穷大,输出距离,如果是无穷大,输出-1.
return 0;
}
这段代码通过邻接表表示图,并使用广度优先搜索(BFS)来寻找从节点1到给定图中其他所有节点的最短距离。这种技术通常应用于无权图中查找单源最短路径问题。以下是对关键部分的分析:
邻接表建立
通过函数 add(int a, int b) 添加边 (a, b) 到邻接表,实现步骤包括:
- 将边
b的信息添加到数组e中。 - 将
a的当前邻接表头(h[a])作为b的下一条边的位置信息(ne[idx])。 - 用
idx更新a的邻接表头,即h[a] = idx;,并将idx自增,以便将下一条边添加到数组中。 初始时,所有h的值都设置为-1,表示该节点的邻接表为空。
广度优先搜索
bfs() 函数通过队列 q 实现广度优先搜索。主要步骤如下:
dist数组用-1初始化,表示所有节点到1号节点的距离未知或不可达。- 从1号节点开始搜索,将其设置为起始节点(
dist[1] = 0),并放入队列q。 - 队列非空时,取出队列头部节点
t,遍历其所有可达节点j。 - 对于未访问过的节点,更新它们到起始节点1的距离,并将它们入队准备下一轮搜索。
主函数
在 main() 函数中,读取图的节点数 n 和边数 m。对于输入的每条边 (a, b),使用 add() 函数构建邻接表。随后调用 bfs() 函数执行广度优先搜索,来查找从节点1到所有节点的最短距离。
最后,检查 dist[n] 的值。如果其值不为 -1,则输出从1到 n 的距离;否则,表示节点 n 不可达,输出 -1。
关于 dist 初始化为 -1 的解释
在初始化 dist 数组时使用 -1 具有特殊意义:
-1在此上下文中代表无穷大,即尚未发现从起点到该节点的路径。- 在 BFS 进行过程中,如果识别出一个新的最短路径,则
dist数组对应元素将被更新为有效的距离值。 - 这种初始化方法方便在完成 BFS 后检查节点是否可达,因为如果某个
dist[i]仍然是-1,则表示节点i从节点1不可达。
这种方法是标准广度优先搜索的一个常见实现手段。
对于这类最短路径问题,当图中所有边的长度相同(在本题中,每条边的长度都为1)时,宽度优先搜索(BFS)是求解最短路径的理想算法。这是因为BFS按层逐渐扩展,能够保证当你第一次发现某个节点时,就是从起始点出发到该节点的最短路径。
BFS的核心在于,它用一个队列维护了一个待扩展的节点集合,并且始终保证在队列前面的节点是最早被发现的。因此,当你从起始节点开始逐层进行搜索时,一旦找到目标节点,就可以保证所走过的路径是最短的。
在给定题目的场景中,我们从点1开始,使用BFS算法搜索每条出边,因为所有边的长度都为1,所以每到达一个新节点,其路径长度(距离)就是前一个节点的路径长度加1。继续这个过程,直到我们到达点n或者所有节点都被访问过。如果我们在搜索过程中能到达点n,就可以确定找到了从点1到点n的最短路径。若在搜索结束时,点n仍然未被访问,则说明点1无法到达点n,即答案是-1。
总结来说,BFS可以在无权图(所有边权重相等的图)中高效地找到两点之间的最短路径,这是因为其按照路径长度的增序来访问节点,一旦找到目标节点,就不需要进一步的搜索了。因此,对于本题,给出所有边长度相等为1的有向图,BFS是解决最短路径问题的最合适的算法。
在宽度优先搜索 (BFS) 中使用 memset(dist, -1, sizeof dist) 这一语句的目的是为了初始化整个 dist 数组,将所有元素的值设为 -1。这样做有以下几个原因:
- 未访问标记:
-1在这里通常用来表示一个节点从起始节点是不可达的或尚未被访问过。在算法的初始状态,我们假设除了起点外,所有节点都未被探索过。 - 距离的默认值:在 BFS 中,
dist数组用于记录从起点到每个节点的最短距离。将数组的所有值初始化为-1,可以在算法执行过程中轻松检查哪些节点已经被计算了距离(值不再是-1),哪些节点还没有(值仍然是-1)。 - 防止错误计算:当你最终检查
dist[n]的值时,如果它是-1,就可以清晰地知道节点n是不可从起始节点达到的,因此可以输出-1(就像题目所要求的那样)。如果该节点的距离被正确地计算过了,它的值应当是一个非负整数。
具体到这个题目,从 1 号点出发在有向图中搜索到 n 号点的最短距离,若最后 dist[n] 的值仍然是 -1,说明 1 号点无法到达 n 号点。在 BFS 的每个迭代中,一旦发现一个节点(首次从队列中获得),则该节点的 dist 值将被更新为从起点开始的距离值,而不再是 -1。 初始化这些值为 -1 是一种常见的编程技巧,用于标记所有节点在搜索开始时都是新的或未被探索过的。这种方法简单高效且易于实现。
在这段代码的上下文中,队列 q 被用来实现广度优先搜索 (BFS)。在 BFS 算法中,队列是一个核心的数据结构,用于按序访问图中的顶点,并保证访问顺序符合“最先进入,最先退出”的原则。下面是队列 q 在 BFS 中的具体作用:
- 维护待访问的顶点集合:
- 队列
q存储了已经被发现但其相邻顶点尚未全部被检查的顶点集合。也就是说,队列中的顶点是接下来将要访问其邻接顶点的。
- 队列
- 顺序访问:
- 队列先入先出 (FIFO) 的特性保证了顶点被访问的顺序:最先被发现的顶点将最先被处理,这对于 BFS 寻找最短路径非常关键。
- 层级遍历:
- BFS 使用队列实现了逐层遍历图的能力。每从队列中取出一个顶点,算法就检查其所有未访问的邻接顶点,并将它们添加到队列尾部。由此保证了顶点按从起点开始计算的距离层次进行扩展。
- 防止重复访问:
- 当一个顶点被放入队列
q时,以某种方式标记该顶点(比如本段代码中的数组st),这可以保证同一个顶点不会被多次放入队列,从而避免重复处理。
- 当一个顶点被放入队列
- 记录距离:
- 对于求最短路径的问题,队列还可以用来记录从起始顶点到当前顶点的路径长度。每次从队列中取出一个顶点
t,算法更新所有邻接顶点未被访问的dist值为dist[t] + 1。
- 对于求最短路径的问题,队列还可以用来记录从起始顶点到当前顶点的路径长度。每次从队列中取出一个顶点
在给定的代码片段中,队列 q 就是用来顺序地存储和处理图中的顶点,并逐渐扩展已经走过的路径。通过不断地将新发现的顶点添加到队列中,并移除并处理队列头部的顶点,BFS 能够在不创建循环依赖的情况下遍历整个图。在这个过程中,BFS 是无向图和有向图中用来找最短路径的有效算法(在边的权重都相等或不存在的情况下)。

浙公网安备 33010602011771号