news 2026/4/16 16:44:36

C语言图论:最短路径算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言图论:最短路径算法

本文献给:
已掌握无向图基础,希望理解如何在带权图中找到两点间最短路径的C语言学习者。本文将系统讲解两种经典的最短路径算法。


你将学到:

  1. 最短路径问题的定义与核心概念
  2. Dijkstra算法:解决单源、非负权图的最短路径
  3. Bellman-Ford算法:处理单源、含负权边的最短路径
  4. 算法对比与实战应用


目录

  • 第一部分:问题定义与核心概念
    • 1. 什么是最短路径?
  • 第二部分:图的存储(带权图)
  • 第三部分:Dijkstra算法
  • 第四部分:Bellman-Ford算法
    • 1. 核心思想
    • 2. 算法步骤
    • 3. C语言实现
  • 第五部分:总结与对比
    • 算法对比表
    • 选择指南

第一部分:问题定义与核心概念

1. 什么是最短路径?

在带权无向图G = ( V , E , w ) G = (V, E, w)G=(V,E,w)中,w : E → R w: E \rightarrow \mathbb{R}w:ER为边权函数。从源点s ss到目标t tt的路径P = ( s , v 1 , . . . , v k , t ) P = (s, v_1, ..., v_k, t)P=(s,v1,...,vk,t)的长度为路径上所有边权之和:
d i s t ( P ) = ∑ i = 0 k w ( v i , v i + 1 ) dist(P) = \sum_{i=0}^{k} w(v_i, v_{i+1})dist(P)=i=0kw(vi,vi+1)
最短路径是所有s sst tt路径中长度最小的路径。


关键术语:

  • 单源最短路径:求从一个源点s ss到图中所有其他顶点的最短距离。
  • 权值:可正、可负(实际问题中常为非负,如距离、时间、成本)。
  • 松弛操作:最短路径算法的核心操作,通过考察一条边来更新当前的最短距离估计。

第二部分:图的存储(带权图)

在基础邻接矩阵/邻接表上,需要增加权值信息。

#defineMAX_V100#defineINF0x3f3f3f3f// 表示“无穷大”的一个较大数值// 邻接矩阵(带权)typedefstruct{intmatrix[MAX_V][MAX_V];// 存储权值,INF表示无边intvertex_count;}GraphMatrixWeighted;voidinit_graph_weighted(GraphMatrixWeighted*g,intn){g->vertex_count=n;for(inti=0;i<n;i++){for(intj=0;j<n;j++){g->matrix[i][j]=(i==j)?0:INF;// 自身距离为0}}}voidadd_edge_weighted(GraphMatrixWeighted*g,intu,intv,intweight){if(u>=g->vertex_count||v>=g->vertex_count)return;g->matrix[u][v]=weight;g->matrix[v][u]=weight;// 无向图}

第三部分:Dijkstra算法

1. 核心思想

贪心策略。维护一个集合S SS,包含已确定最短距离的顶点。每次从未确定的顶点中选择距离源点s ss最近的顶点u uu加入S SS,并用u uu松弛其所有邻居的距离。要求:所有边权非负

算法正确性依赖:当边权非负时,当前未确定顶点中距离最小的顶点,其距离不可能再被其他未确定的顶点更新减小。


2. 算法步骤(朴素O ( ∣ V ∣ 2 ) O(|V|^2)O(V2)实现)

  1. 初始化:dist[s] = 0dist[v] = INFv ≠ s),visited[v] = false
  2. 循环|V|次:
    a. 找到未访问顶点中dist最小的顶点u
    b. 标记u为已访问 (visited[u] = true)。
    c.松弛操作:对u的每个邻居v,如果dist[u] + w(u, v) < dist[v],则更新dist[v] = dist[u] + w(u, v)
  3. 循环结束,dist[]数组即为源点到各点的最短距离。

3. C语言实现

// Dijkstra算法 (邻接矩阵,朴素实现)voiddijkstra(GraphMatrixWeighted*g,intsrc,intdist[]){intvisited[MAX_V]={0};// 初始化for(inti=0;i<g->vertex_count;i++){dist[i]=INF;}dist[src]=0;for(intcount=0;count<g->vertex_count;count++){// 步骤1: 找到未访问的dist最小顶点intu=-1;intmin_dist=INF;for(intv=0;v<g->vertex_count;v++){if(!visited[v]&&dist[v]<min_dist){min_dist=dist[v];u=v;}}if(u==-1)break;// 剩余顶点不可达visited[u]=1;// 步骤2: 松弛u的所有邻居for(intv=0;v<g->vertex_count;v++){if(!visited[v]&&g->matrix[u][v]!=INF){intnew_dist=dist[u]+g->matrix[u][v];if(new_dist<dist[v]){dist[v]=new_dist;}}}}}

算法复杂度:

  • 时间复杂度O ( ∣ V ∣ 2 ) O(|V|^2)O(V2),适合稠密图。
  • 空间复杂度O ( ∣ V ∣ ) O(|V|)O(V)
  • 优化方向:使用**优先队列(最小堆)**可将时间复杂度降为O ( ( ∣ V ∣ + ∣ E ∣ ) log ⁡ ∣ V ∣ ) O((|V|+|E|) \log |V|)O((V+E)logV),适合稀疏图。

第四部分:Bellman-Ford算法

1. 核心思想

动态规划/松弛。通过对所有边进行∣ V ∣ − 1 |V|-1V1轮松弛操作,逐步逼近最短路径。可以处理负权边,并能检测出图中是否存在从源点可达的负权环

原理:在无负权环的图中,任意两点间的最短路径最多包含∣ V ∣ − 1 |V|-1V1条边。因此通过∣ V ∣ − 1 |V|-1V1轮全局松弛足以找到所有最短路径。


2. 算法步骤

  1. 初始化:dist[s] = 0dist[v] = INFv ≠ s)。
  2. 进行∣ V ∣ − 1 |V|-1V1轮迭代,每轮:
    • 遍历图中的所有边( u , v ) (u, v)(u,v)
    • 对每条边执行松弛操作:如果dist[u] + w(u, v) < dist[v],则更新dist[v] = dist[u] + w(u, v)
  3. 负权环检测:再进行一轮所有边的遍历。如果仍有边能被松弛,则说明图中存在从源点可达的负权环,最短路径无意义。

3. C语言实现

// Bellman-Ford算法 (使用边列表存储图更合适,此处为演示使用邻接矩阵遍历所有边)voidbellman_ford(GraphMatrixWeighted*g,intsrc,intdist[]){// 初始化for(inti=0;i<g->vertex_count;i++){dist[i]=INF;}dist[src]=0;// 步骤1: 进行 |V|-1 轮松弛for(inti=0;i<g->vertex_count-1;i++){// 遍历所有边 (u, v)for(intu=0;u<g->vertex_count;u++){for(intv=0;v<g->vertex_count;v++){if(g->matrix[u][v]!=INF){// 存在边intnew_dist=dist[u]+g->matrix[u][v];if(new_dist<dist[v]){dist[v]=new_dist;}}}}}// 步骤2: 检测负权环 (可选,如果需要检测)for(intu=0;u<g->vertex_count;u++){for(intv=0;v<g->vertex_count;v++){if(g->matrix[u][v]!=INF&&dist[u]+g->matrix[u][v]<dist[v]){printf("图中存在从源点可达的负权环!\n");return;}}}}

算法复杂度:

  • 时间复杂度O ( ∣ V ∣ ⋅ ∣ E ∣ ) O(|V| \cdot |E|)O(VE),使用邻接矩阵为O ( ∣ V ∣ 3 ) O(|V|^3)O(V3),使用边列表为O ( ∣ V ∣ ⋅ ∣ E ∣ ) O(|V| \cdot |E|)O(VE)
  • 空间复杂度O ( ∣ V ∣ ) O(|V|)O(V)

第五部分:总结与对比

算法对比表

特性Dijkstra算法Bellman-Ford算法
适用图类型非负权图任意图(可含负权边)
核心思想贪心动态规划/松弛
时间复杂度O ( ∣ V ∣ 2 ) O(|V|^2)O(V2)O ( ( ∣ V ∣ + ∣ E ∣ ) log ⁡ ∣ V ∣ ) O((|V|+|E|)\log|V|)O((V+E)logV)O ( ∣ V ∣ ⋅ ∣ E ∣ ) O(|V| \cdot |E|)O(VE)
空间复杂度O ( ∣ V ∣ ) O(|V|)O(V)O ( ∣ V ∣ ) O(|V|)O(V)
功能求单源最短路径求单源最短路径,可检测负权环
优点在非负权图中效率高功能强大,适用范围广
缺点不能处理负权边时间复杂度高

选择指南

  1. 绝大多数情况(边权非负):优先使用Dijkstra算法(尤其是堆优化版本)。例如:道路导航、网络路由。
  2. 图含有负权边:必须使用Bellman-Ford算法。例如:金融套利模型、某些特殊约束问题。
  3. 需要检测负权环:使用 Bellman-Ford 算法。



觉得文章有帮助?别忘了:

👍 点赞 👍- 给我一点鼓励
⭐ 收藏 ⭐- 方便以后查看
🔔 关注 🔔- 获取更新通知



标签:#C语言#图论#最短路径#Dijkstra#Bellman-Ford#算法

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:18:14

基于django智慧农业管理系统

目录 摘要 演示视频 系统功能实现 代码实现 推荐项目 项目案例 项目开发总结 为什么选择我 源码获取 博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于…

作者头像 李华
网站建设 2026/4/16 13:32:18

实习面试题-PHP 面试题

1.在 PHP 中,如何实现批量操作数据库记录? 回答重点 在 PHP 中实现批量操作数据库记录,常常通过以下几种方式: 1)批量插入:可以使用多值插入(Multiple Values Insert)的方法,通过一个 SQL 语句插入多条记录。 2)批量更新:可以使用批量更新(Bulk Update)的方法,…

作者头像 李华
网站建设 2026/4/16 10:15:59

深入理解 IndexedDB:在浏览器中存储 PB 级数据的事务性 API 实战

各位同仁、技术爱好者们&#xff0c;大家好&#xff01; 今天&#xff0c;我们将深入探讨一个在现代Web开发中至关重要的API——IndexedDB。随着Web应用复杂性的日益增加&#xff0c;以及对离线工作能力、高性能数据处理的需求不断提升&#xff0c;浏览器内置的存储机制面临着…

作者头像 李华
网站建设 2026/4/16 10:13:44

大数据领域体系认知

目录 大数据时代 大数据时代背景 大数据概念 大数据发展史 大数据的应用 国家大数据发展战略 大数据与其他前沿技术 大数据基础知识 大数据处理全流程 大数据时代 大数据时代的来临并非偶然。 大数据时代背景 ①数据产生方式推动了大数据时代的来临&#xff1a; 运…

作者头像 李华
网站建设 2026/4/16 10:13:41

储能系统双向 DCDC 变换器双闭环控制:解锁蓄电池充放电仿真的奥秘

储能系统双向DCDC变换器双闭环控制 蓄电池充放电仿真模型有buck模式和boost模式&#xff0c;依靠蓄电池充放电维持直流母线电压平衡在储能系统这个充满魅力的领域&#xff0c;双向 DCDC 变换器的双闭环控制犹如一颗璀璨的明珠&#xff0c;它对蓄电池充放电的精准把控&#xff0…

作者头像 李华