news 2026/6/15 18:22:35

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/6/15 11:32:56

软件工程期末考试-数据流图、状态图、用例图、类图等怎么画?

分类 概念问答数据流图软件结构图状态图流程图&#xff0c;盒图&#xff0c;pad图白盒测试/黑盒测试用例图类图事件跟踪图项目管理概念问答 1)概念问答什么是软件工程 把系统的、规范的途径应用于软件开发和维护过程&#xff0c;也就是把工程应用于软件研究上面提到的途径什么是…

作者头像 李华
网站建设 2026/6/15 11:36:16

AI应用层革命(七)——智能体的终极形态:认知循环体的诞生

AI应用层革命&#xff08;七&#xff09;——智能体的终极形态&#xff1a;认知循环体的诞生本文为《AI应用层革命》系列第七篇&#xff0c;承接前六篇对智能体自主演化、伦理边界与法律框架的系统性探讨。本篇将深入剖析智能体发展的终极方向——认知循环体&#xff08;Cognit…

作者头像 李华
网站建设 2026/6/15 16:32:58

背单词项目

1.v1&#xff08;第一版比较简陋&#xff0c;反正也是先实验&#xff09;:首先&#xff0c;创建随机对象和有获取功能的对象接着&#xff0c;创建字符串数组存入单词和相应的中文最后就是背单词软件的逻辑&#xff0c;先学习一下其中具体的方法&#xff1a;nextInt&#xff1a;…

作者头像 李华
网站建设 2026/6/15 11:47:12

15、UNIX内核基础与配置详解

UNIX内核基础与配置详解 1. 为何要了解UNIX内核 在日常系统管理工作中,如添加用户、运行作业、打印文件、执行备份恢复,甚至开关机等操作,似乎不需要深入了解UNIX内核。但实际上,如果从不添加硬件、不调整系统以提升性能,确实无需过多了解内核。然而,在多年的系统管理经…

作者头像 李华