【数据结构】图论最短路径算法深度解析:从BFS基础到全算法综述

最短路径

导读

大家好,很高兴又和大家见面啦!!!

欢迎继续探索图算法的精彩世界!在上一篇博客中,我们研究了最小生成树(MST)问题 ------它专注于为整个连通图寻找一棵连接所有顶点且总权重最小的"骨架树",就像铺设覆盖整个城市且成本最低的电网。

今天,我们将目光转向一个同样关键但目标不同的单点决策问题:最短路径。想象你站在地图上的一个特定起点(比如"家"),目标是以最小的累计代价(时间、距离、费用)到达图中另一个点或所有其他点。这就是最短路径问题的核心!

最短路径 vs. 最小生成树

  • MST追求的是全局最优连接(覆盖所有点)。
  • 最短路径追求的是点到点的最优路径(关注起点到特定终点的序列)。

本篇核心内容: 我们将

  • 清晰理解最短路径的基本概念和问题分类(单源SSSP、所有顶点对APSP)。

  • 简要罗列解决这些问题的经典算法(如Dijkstra用于非负权、Floyd用于所有顶点对)。

  • 重点探讨 第一种实战场景:如何利用 BFS(广度优先搜索)算法,高效求解非带权图(边权视为1) 中的单源最短路径问题------即从起点出发,到达图中任一点的最少边数/跳数路径。

  • 深入理解 BFS用于 最短路径的核心思路 和 C语言实现细节。

你是否好奇原本用于图遍历的BFS,如何巧变身为求解最短路径的神器?它能否处理带权重的复杂道路?这些问题的答案,将在正文中一一揭晓。让我们立刻开始这段从起点寻找最优路线的旅程!

一、最短路径

要理解什么是最短路径,这里我们以一个比较形象的例子来进行理解:
5 3 3 8 12 5 学校 家 食堂 网吧

在这个地图中,如果我们从家里出发要去学校的话,我们有多条路径可供选择,这里我们例举部分路径:

  • 家->学校:该路径代价为5min
  • 家->食堂->学校:该路径代价为6min
  • 家->网吧->学校:该路径代价为20min
  • 家->网吧->食堂->学校:该路径代价为16min
  • ......

从这些路径中我们不难发现我们直接选择:家->学校这条路径所花费的代价是最小的,因此这条路径就是最短路径。

下面我们就来了解以下最短路径的定义:

最短路径:​​ 指在起点和终点之间​​所有可能路径中​​,其​​边上权重总和最小的​​那条路径。​​路径是否最短取决于你如何定义边的权重​​(是距离、时间还是成本?)。

问题本质是:在图(由点和连接点的线组成)中,以某种代价(距离、时间、费用等)为衡量标准,寻找从起点到终点的最优路线,使得累计代价最小。​​

1.1 单源最短路径

单源最短路径 ​​(Single-Source Shortest Path, SSSP )是图论中的核心问题,旨在解决:​​给定一个带权图和指定的起点(源点),计算从该起点到图中所有其他顶点的最短路径及其距离​​。

不局限于单个终点 ,而是生成一张覆盖全图 的"最短路径地图",为后续任意终点查询提供基础。

1.2 各顶点间的最短路径

所有顶点间的最短路径All-Pairs Shortest Paths, APSP )​​ 是图论中的一个核心问题,其目标是计算给定带权图中​​每一对顶点之间​​的最短路径及其距离

简单来说,就是求解图中​​任意起点 ​​到​​任意终点​​最短路径问题

1.3 最短路径算法

在最短路径问题中,主要有以下算法用于解决最短路径问题:

  • BFS广度优先搜索

    • 适用场景:无权图(边权=1),求最少边数路径(最小跳数)。
    • 时间复杂度 : O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
    • 核心思想:按层次遍历图,首次到达终点的路径即最短路径。
  • Dijkstra 算法

    • 适用场景:非负权重图(有向/无向)。
    • 时间复杂度
      • 朴素实现 : O ( ∣ V ∣ 2 + ∣ E ∣ ) O(|V|^2 + |E|) O(∣V∣2+∣E∣)
      • 堆优化主流 ): O ( ( ∣ V ∣ + ∣ E ∣ ) l o g ∣ V ∣ ) O((|V|+|E|)log|V|) O((∣V∣+∣E∣)log∣V∣)
    • 核心思想:贪心策略 + 优先队列。每次从未确定点中选取距离起点最近的顶点,松弛其邻居。
    • 关键点
      • 需用优先队列(最小堆) 加速。
      • 不能处理负权边(可能导致错误结果)。
  • Bellman-Ford 算法

    • 适用场景:含负权边图(无负环),可检测负权环。
    • 时间复杂度 : O ( ∣ V ∣ ∣ E ∣ ) O(|V||E|) O(∣V∣∣E∣)
    • 核心思想 : 进行 ∣ V ∣ − 1 |V|-1 ∣V∣−1 轮松弛操作(每轮遍历所有边),第 ∣ V ∣ |V| ∣V∣ 轮检测负环。
  • A 算法*

    • 适用场景:非负权重图 + 有明确终点 + 启发函数(加速搜索)。
    • 时间复杂度 :取决于启发函数质量(最坏仍 O ( ( ∣ V ∣ + ∣ E ∣ ) l o g ∣ V ∣ ) O((|V|+|E|)log|V|) O((∣V∣+∣E∣)log∣V∣))。
    • 核心思想 :扩展 Dijkstra,引入启发函数 h ( v ) h(v) h(v)(估算 v v v 到终点的代价),优先扩展 f ( v ) = g ( v ) + h ( v ) f(v)=g(v)+h(v) f(v)=g(v)+h(v) 最小的点( g ( v ) g(v) g(v) 为起点到 v v v 的实际代价)。
    • 要求 : h ( v ) h(v) h(v) 必须为可采纳启发函数(不能高估真实代价)。
    • 应用 :游戏寻路、机器人导航(如 h ( v ) h(v) h(v) 用欧式距离)。
  • Floyd-Warshall 算法

    • 适用场景:所有顶点对最短路径(APSP),支持负权边(无负环)。
    • 时间复杂度 : O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3)
    • 核心思想 : 动态规划。定义 d i s t [ i ] [ j ] dist[i][j] dist[i][j] 为通过顶点 1.. k 1..k 1..k 中转时 i → j i→j i→j 的最短距离,迭代更新 k k k。
    • 核心公式 : d i s t [ i ] [ j ] = m i n ( d i s t [ i ] [ j ] , d i s t [ i ] [ k ] + d i s t [ k ] [ j ] ) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])
  • Johnson 算法(高效处理带负权的 APSP)

    • 适用场景:含负权边的稀疏图(无负环)的 APSP 问题。
    • 时间复杂度 : O ( ∣ V ∣ ∣ E ∣ l o g ∣ V ∣ ) O(|V||E| log |V|) O(∣V∣∣E∣log∣V∣) (优于 Floyd 在稀疏图的表现)
    • 步骤
      • 添加虚拟节点 s s s,连接所有顶点(边权=0)。
      • 用 Bellman-Ford 计算 s s s 到各点的最短距离 h ( v ) h(v) h(v)(势能函数)。
      • 重加权边: w ^ ( u , v ) = w ( u , v ) + h ( u ) − h ( v ) ŵ(u,v) = w(u,v) + h(u) - h(v) w^(u,v)=w(u,v)+h(u)−h(v)(新权重非负)。
      • 对每个顶点运行 Dijkstra(基于 w ^ ŵ w^)。
      • 还原真实距离: d i s t ( u , v ) = d i s t ^ ( u , v ) − h ( u ) + h ( v ) dist(u,v) = dist̂(u,v) - h(u) + h(v) dist(u,v)=dist^(u,v)−h(u)+h(v)

各算法对应的应用场景如下所示:

场景需求 推荐算法
无权图(最少边数) BFS
非负权图(单源最短路径) Dijkstra
含负权边(单源,需检测负环) Bellman-Ford
非负权图 + 终点明确 + 需加速 A*
小规模图或稠密图(所有点对最短路径) Floyd-Warshall
含负权的稀疏图(所有点对) Johnson

在现阶段,我们主要需要了解3种算法:

  • 单源路径算法(SSSP):
    • BFS算法
    • Dijkstra算法
  • 各顶点间的最短路径(APSP):
    • Floyd算法

二、BFS算法

BFS算法用于解决非带权图的单源最短路径问题。所谓的非带权图,我们可以视作各边权值为1的特殊带权图。

在之前我们有介绍过广度优先生成树,整个生成树的创建过程实际上就是由近到远的图遍历过程:

  • 生成树的根结点就是单源最短路径中的起点(源点)
  • 从根结点到各结点的路径就是单源最短路径中起点到各结点的最短路径

因此整个算法的实现我们可以参考广度优先生成树的算法实现:

  • 通过visited[]数组来记录当前顶点是否被访问
  • 通过d[]数组来记录当前顶点到源点的最短路径
  • 通过path[]数组来记录当前顶点的前驱顶点
  • 通过队列queue[]来实现整个遍历过程
  • 算法的核心逻辑为 BFS
  • 对未访问顶点的处理:
    • 通过d[]来记录当前顶点到原点的路径长度
    • 通过path[]来记录当前顶点的前驱顶点
    • 通过visited[]来更改当前顶点的访问状态

对于BFS算法的核心逻辑,我们在广度优先遍历的篇章中已经详细介绍,这里就不再展开,感兴趣的朋友可以点击链接阅读相关内容。

下面我们直接来看C语言代码:

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 ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)

上述算法是在连通图中求解单源最短路径问题,如果是在非连通图中,我们可以通过遍历visited[]来找到未被访问的顶点。这个问题在广度优先遍历的章节中同样有过介绍,这里我就不再赘述。

结语

内容回顾

通过本篇博客,我们一起深入探讨了最短路径问题的核心概念与应用场景。主要内容总结如下:

  • 最短路径问题本质

    • 在带权图中寻找起点到终点之间累计权重最小的路径(如最短时间、最短距离或最小成本路径),是解决实际路线规划问题的理论基础。
  • 问题分类与算法全景

    • 单源最短路径(SSSP):从指定起点到所有其他顶点的最短路径(如导航起点到全城地点)。
    • 所有顶点间的最短路径(APSP):计算图中任意两点间的最短路径(如城市交通网全局分析)。
  • 简要介绍了六类主流算法BFSDijkstraBellman-FordA*FloydJohnson及其适用场景

  • BFS算法的实战应用

    • 重点解析了如何利用广度优先搜索(BFS)高效解决无权图的最短路径问题:
      • 将无权图视为边权均为1的特殊带权图,通过 d[]数组记录距离、path[]数组回溯路径
      • 基于层次遍历特性,首次访问的路径即为最短路径,时间复杂度仅 O ( V + E ) O(V + E ) O(V+E)
      • 提供完整C语言实现代码,适用于网络路由、社交关系等最小跳数应用场景

下一篇预告:挑战带权最短路径!

至此,我们掌握了无权图的最短路径解决方案。但当图中路径的代价不再均等(如公路有长短、任务有耗时)时,BFS就无法满足需求了!

下一篇博客将聚焦两类核心算法:

🔹 Dijkstra算法

  • 解决带非负权重图的单源最短路径问题,用贪心策略+优先队列实现高效搜索。你将学到:
    • 如何通过 "松弛操作" 动态更新最短路径
    • 堆优化实现细节与时间复杂度分析
    • 为何它无法处理负权边

🔹 Floyd算法

  • 一网打尽所有顶点间的最短路径(APSP),动态规划的经典应用:
    • 三重循环背后的子问题分解思想
    • 中转顶点k的迭代优化逻辑
    • 负权环的检测方法

代码实战预告:下一篇将提供两类算法的C语言实现,手把手带你写出高效的最短路径计算器!

觉得这篇博客有帮助? ❤️ 您的支持是我持续创作的动力!

👉 关注我,第一时间获取技术干货更新

👍 点赞收藏,方便日后复习查找

💬 评论提问或分享你的见解,我们一起探讨

🔄 转发给需要的朋友,共同进步!

下一站,我们将在加权图的复杂道路中寻找最优解------DijkstraFloyd算法深度解析,不见不散!

相关推荐
Splendid12 分钟前
Geneformer:基于Transformer的基因表达预测深度学习模型
javascript·算法
愿所愿皆可成26 分钟前
机器学习之聚类Kmeans算法
算法·机器学习·kmeans·聚类
幻奏岚音26 分钟前
统计学(第8版)——假设检验学习笔记(考试用)
笔记·学习·算法
hie988941 小时前
基于matlab策略迭代和值迭代法的动态规划
算法·动态规划
Coovally AI模型快速验证1 小时前
SFTrack:面向警务无人机的自适应多目标跟踪算法——突破小尺度高速运动目标的追踪瓶颈
人工智能·神经网络·算法·yolo·计算机视觉·目标跟踪·无人机
Brduino脑机接口技术答疑1 小时前
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
人工智能·算法·脑机接口·新手入门
真的很上进1 小时前
2025最全TS手写题之partial/Omit/Pick/Exclude/Readonly/Required
java·前端·vue.js·python·算法·react·html5
linweidong1 小时前
GO 基础语法和数据类型面试题及参考答案(下)
算法·后端面试·泛型·go面试·go面经·go求职
圈圈编码2 小时前
LeetCode Hot100刷题——合并两个有序链表
java·数据结构·算法·leetcode·链表
老歌老听老掉牙2 小时前
旋量理论:刚体运动的几何描述与机器人应用
python·算法·机器学习·机器人·旋量