
大家好,很高兴又和大家见面啦!!!
欢迎继续探索图算法的精彩世界!在上一篇博客中,我们研究了最小生成树(MST)问题——它专注于为整个连通图寻找一棵连接所有顶点且总权重最小的“骨架树”,就像铺设覆盖整个城市且成本最低的电网。
今天,我们将目光转向一个同样关键但目标不同的单点决策问题:最短路径。想象你站在地图上的一个特定起点(比如“家”),目标是以最小的累计代价(时间、距离、费用)到达图中另一个点或所有其他点。这就是最短路径问题的核心!
最短路径 vs. 最小生成树:
本篇核心内容: 我们将
你是否好奇原本用于图遍历的BFS,如何巧变身为求解最短路径的神器?它能否处理带权重的复杂道路?这些问题的答案,将在正文中一一揭晓。让我们立刻开始这段从起点寻找最优路线的旅程!
要理解什么是最短路径,这里我们以一个比较形象的例子来进行理解:
graph LR
a[学校]----|5|b[家]----|3|d[食堂]----|3|a
b----|8|c[网吧]----|12|a
d----|5|c在这个地图中,如果我们从家里出发要去学校的话,我们有多条路径可供选择,这里我们例举部分路径:
从这些路径中我们不难发现我们直接选择:家->学校这条路径所花费的代价是最小的,因此这条路径就是最短路径。
下面我们就来了解以下最短路径的定义:
最短路径: 指在起点和终点之间所有可能路径中,其边上权重总和最小的那条路径。路径是否最短取决于你如何定义边的权重(是距离、时间还是成本?)。
其问题本质是:在图(由点和连接点的线组成)中,以某种代价(距离、时间、费用等)为衡量标准,寻找从起点到终点的最优路线,使得累计代价最小。
单源最短路径(Single-Source Shortest Path, SSSP)是图论中的核心问题,旨在解决:给定一个带权图和指定的起点(源点),计算从该起点到图中所有其他顶点的最短路径及其距离。
它不局限于单个终点,而是生成一张覆盖全图的“最短路径地图”,为后续任意终点查询提供基础。
所有顶点间的最短路径(All-Pairs Shortest Paths, APSP) 是图论中的一个核心问题,其目标是计算给定带权图中每一对顶点之间的最短路径及其距离。
简单来说,就是求解图中任意起点到任意终点的最短路径问题。
在最短路径问题中,主要有以下算法用于解决最短路径问题:
各算法对应的应用场景如下所示:
| 场景需求 | 推荐算法 | 
|---|---|
| 无权图(最少边数) | BFS | 
| 非负权图(单源最短路径) | Dijkstra | 
| 含负权边(单源,需检测负环) | Bellman-Ford | 
| 非负权图 + 终点明确 + 需加速 | A* | 
| 小规模图或稠密图(所有点对最短路径) | Floyd-Warshall | 
| 含负权的稀疏图(所有点对) | Johnson | 
在现阶段,我们主要需要了解3种算法:
BFS算法用于解决非带权图的单源最短路径问题。所谓的非带权图,我们可以视作各边权值为1的特殊带权图。
在之前我们有介绍过广度优先生成树,整个生成树的创建过程实际上就是由近到远的图遍历过程:
因此整个算法的实现我们可以参考广度优先生成树的算法实现:
visited[]数组来记录当前顶点是否被访问d[]数组来记录当前顶点到源点的最短路径path[]数组来记录当前顶点的前驱顶点queue[]来实现整个遍历过程BFSd[]来记录当前顶点到原点的路径长度path[]来记录当前顶点的前驱顶点visited[]来更改当前顶点的访问状态对于BFS算法的核心逻辑,我们在广度优先遍历的篇章中已经详细介绍,这里就不再展开,感兴趣的朋友可以点击链接阅读相关内容。
下面我们直接来看C语言代码:
int visited[MAXVERSIZE];				// 遍历标记数组
int d[MAXVERSIZE];						// 路径长度数组
int path[MAXVERSIZE];					// 路径前驱数组
void BFS_MIN_Distance(graph* g, int u) {
	for (int i = 0; i < MAXVERSIZE; i++) {
		visited[i] = false;				// 初始化所有顶点为未访问状态
		d[i] = -1;						// 初始化顶点与源点不存在路径
		path[i] = -1;					// 初始化所有顶点没有前驱顶点
	}
	visited[u] = true;					// 访问源点
	d[u] = 0;							// 源点距离源点路径长度为0
	path[u] = -1;						// 源点不存在前驱顶点
	Queue* Q = CreatQueue();			// 遍历队列
	EnQueue(Q, u);						// 源点入队
	while (!IsEmpty(Q)) {
		int p = DeQueue(Q);				// 队首元素出队
		for (int w = FirstNeighbor(g, p); w >= 0; w = NextNeighbor(g, p, w)) {
			if (!visited[w]) {
				visited[w] = true;		// 访问当前顶点
				path[w] = p;			// 当前顶点的前驱顶点为p
				d[w] = d[p] + 1;		// 当前顶点距离源点的距离为顶点p距离源点的距离+1
				EnQueue(Q, w);			// 当前顶点入队
			}
		}
	}
	DestroyQueue(&Q);					// 销毁队列
}BFS 求解单源最短路径问题的思路很简单,只需要在广度优先生成树的基础上额外维护两个数组用于记录顶点距离源点的最短路径和其前驱顶点即可。
由于解决的是非带权图的单源最短路径问题,因此我们只需要对图完成最基本的BFS即可,整个算法的时间复杂度为:O(|V|+|E|)
上述算法是在连通图中求解单源最短路径问题,如果是在非连通图中,我们可以通过遍历visited[]来找到未被访问的顶点。这个问题在广度优先遍历的章节中同样有过介绍,这里我就不再赘述。
通过本篇博客,我们一起深入探讨了最短路径问题的核心概念与应用场景。主要内容总结如下:
BFS、Dijkstra、Bellman-Ford、A*、Floyd、Johnson)及其适用场景  。
 BFS)高效解决无权图的最短路径问题: d[]数组记录距离、path[]数组回溯路径至此,我们掌握了无权图的最短路径解决方案。但当图中路径的代价不再均等(如公路有长短、任务有耗时)时,BFS就无法满足需求了!
下一篇博客将聚焦两类核心算法: 🔹 Dijkstra算法
🔹 Floyd算法
代码实战预告:下一篇将提供两类算法的C语言实现,手把手带你写出高效的最短路径计算器!
下一站,我们将在加权图的复杂道路中寻找最优解——Dijkstra与Floyd算法深度解析,不见不散!