1. 引言
在图论中,广度优先搜索(BFS)是一种用于遍历或搜索图的算法,特别适合求解无权图的最短路径问题。本文将结合邻接表存储方式和数组模拟队列的BFS实现,详细讲解如何计算图中点的层次(即节点到起点的最短距离)。
2. 问题描述
给定一个有向图(或无向图),要求从指定的起点(如节点1)出发,计算它到所有其他节点的最短距离(层次)。例如:
-
输入:
复制
下载
4 5 // 4个节点,5条边 1 2 // 边1→2 2 3 // 边2→3 3 4 // 边3→4 1 3 // 边1→3 1 4 // 边1→4
-
输出:
-
节点1到节点2的距离:1
-
节点1到节点3的距离:1
-
节点1到节点4的距离:1
-
3. 邻接表存储图
(1)邻接表的结构
邻接表使用数组模拟链表的方式存储图,由三个数组组成:
-
h[a]
:存储节点a
的第一条边的索引(头指针)。 -
e[i]
:存储第i
条边的终点。 -
ne[i]
:存储第i
条边的下一条边的索引(链表结构)。
(2)邻接表构建过程
以输入样例为例:
-
初始化:
-
h = [-1, -1, -1, -1]
(节点1~4的头指针初始为-1) -
idx = 0
(当前边的索引)
-
-
添加边:
-
add(1, 2)
→e[0]=2
,ne[0]=-1
,h[1]=0
-
add(2, 3)
→e[1]=3
,ne[1]=-1
,h[2]=1
-
add(3, 4)
→e[2]=4
,ne[2]=-1
,h[3]=2
-
add(1, 3)
→e[3]=3
,ne[3]=0
,h[1]=3
-
add(1, 4)
→e[4]=4
,ne[4]=3
,h[1]=4
-
最终邻接表:
节点 | 邻接链表(边的顺序) |
---|---|
1 | 1→4 → 1→3 → 1→2 |
2 | 2→3 |
3 | 3→4 |
4 | (无出边) |
4. BFS算法实现
(1)算法流程
-
初始化:
-
使用队列
q
存储待访问节点。 -
vis
数组记录节点距离,初始为-1
(未访问)。
-
-
起点入队:
-
q = [起点]
,vis[起点] = 0
。
-
-
循环处理队列:
-
取出队首节点
u
。 -
遍历
u
的所有邻接节点v
:-
如果
v
未被访问(vis[v] == -1
),则:-
vis[v] = vis[u] + 1
(更新距离)。 -
v
入队。
-
-
如果
v
是目标节点,可直接返回(优化)。
-
-
5.代码
/* 数组模拟队列bfs,用于求解从节点1到节点n的最短路径 自环:自己指向自己 重边:两个点相互指向对方,形成一个环 */ #include <stdio.h> #include <string.h> #define N 100010 /* h数组:邻接表的头指针数组。h[i] 存储节点 i 的最后一条边的索引。 -1 表示该节点没有邻接边。 不断更新,使得以节点i为首的几条边中,其值是最新的边 e数组:边的终点数组。e[i] 表示第 i 条边的终点。 ne数组:下一条边的索引数组。ne[i] 表示与第 i 条边同起点的下一条边的索引。 idx:当前边的索引。初始为0,每次添加边时递增。 */ int h[N], e[N], ne[N], idx; // 向邻接表中添加一条从节点 a 到节点 b 的边 void add(int a, int b) { e[idx] = b; // 记录边的终点为 b ne[idx] = h[a]; // 新边的下一条边是 h[a] 当前指向的边 h[a] = idx; // 更新 h[a] 为新边的索引 idx++; // 更新边的索引计数器 } /* vis数组:记录节点的访问状态和距离。 vis[i] 为 -1 表示节点 i 未被访问, 否则表示从起点到节点 i 的最短距离。 n:表示目标节点(终点)。 q数组:数组模拟的队列,用于 BFS。 hh:队列头指针(出队位置),初始为0。 tt:队列尾指针(入队位置),初始为-1。 */ int vis[N], n; int q[N], hh, tt = -1; // 从起点 p 开始执行 BFS,计算到其他节点的最短距离 void bfs(int p) { // 初始化 vis 数组为 -1(未访问) memset(vis, -1, sizeof(vis)); // 将起点 p 入队,并设置 vis[p] = 0 (距离为0) q[++tt] = p; // 节点p入队, tt指针后移 vis[p] = 0; // 到起点p的距离为0 // 循环处理队列 while (hh <= tt) { // 取出队头节点 p p = q[hh++]; // hh指针后移,执行出队操作 // 遍历 p 的所有邻接点 for (int i = h[p]; i != -1; i = ne[i]) { int j = e[i]; // 邻接点为j // 如果邻接点 j 未被访问,则将其入队,并更新距离 if (vis[j] == -1) { q[++tt] = j; // 邻接点j入队 vis[j] = vis[p] + 1; // 到j的距离等于到p的距离 + 1 // 如果 j 是目标节点 n,直接返回(提前终止,因为BFS保证了最短路径) if (j == n) return; } } } } int main() { memset(h, -1, sizeof(h)); // 初始化邻接表头指针 int m; scanf("%d%d", &n, &m); // 读入节点数n,边数m // 读入所有边 while (m--) { int a, b; scanf("%d%d", &a, &b); add(a, b); // 添加从a到b的有向边 } // 从节点1开始进行BFS bfs(1); // 输出从节点1到节点n的最短距离 printf("%d\n", vis[n]); // 输出vis[n] return 0; }
6. 示例分析
输入
4 5 1 2 2 3 3 4 1 3 1 4
BFS执行过程
-
初始状态:
-
q = [1]
,vis = [0, -1, -1, -1]
-
-
处理节点1:
-
遍历边
1→4
、1→3
、1→2
。 -
更新
vis = [0, 1, 1, 1]
。 -
入队
q = [1, 4, 3, 2]
。
-
-
处理后续节点:
-
节点4、3、2均无新访问节点,队列清空。
-
-
最终结果:
-
vis = [0, 1, 1, 1]
(节点1到其他节点的距离均为1)。
-
-
BFS是解决无权图最短路径的高效算法,时间复杂度为
O(n + m)
。 -
邻接表适合存储稀疏图,相比邻接矩阵更省空间。
-
数组模拟队列在竞赛编程中更高效,避免了STL容器的开销。
掌握BFS和邻接表的结合,可以轻松解决迷宫问题、社交网络关系链、路由器跳数计算等实际问题!