当前位置: 首页 > news >正文

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#算法

http://www.jsqmd.com/news/88913/

相关文章:

  • 【题解】Luogu P1638 逛画展 Luogu P2564 [SCOI2009] 生日礼物
  • g++演示如何从C++代码到可执行程序
  • 详细介绍:Spring Boot 整合 Thymeleaf(视图层)
  • 电脑音频录制工具(语音聊天录音软件)
  • 【模板】静态区间最值【牛客tracker 每日一题】
  • Ascend C 与 CUDA 的对比分析-为异构计算开发者提供迁移指南
  • CF1004D Sonya and Matrix - crazy-
  • Markdown编辑完全指南
  • DAY37 早停策略和模型权重的保存
  • 西门子1200 PLC自由口通讯CRC校验程序实战
  • 【求解释】智子递归架构:基于互补递归与河洛调控的智能系统框架
  • Node.js `import.meta` 深入全面讲解
  • 影刀RPA发货大杀器!亚马逊订单批量发货效率提升2000%,告别手动煎熬![特殊字符]
  • CF1009F Dominant Indices - crazy-
  • 教程8:结构体的添加和使用-–-behaviac
  • 蓄电池与超级电容器混合储能并网的Simulink仿真探索
  • macOS 的两款好用的免费截图软件: shottr 和 snipaste
  • 教程9:枚举的添加和使用-–-behaviac
  • QSharedMemory 变量在对象析构的时候要怎么处理
  • TikTok达人合作订单太繁琐?影刀RPA一键智能处理,效率飙升10倍![特殊字符]
  • 投机推理原理及设计
  • 前端保存用户登录信息 深入全面讲解
  • 影刀RPA颠覆传统!TikTok售后工单智能处理,效率提升500%[特殊字符]
  • 【开题答辩全过程】以 基于PHP的乐高学习网站管理系统的设计实现为例,包含答辩的问题和答案
  • 【Java毕设全套源码+文档】基于springboot的高校大学生心理咨询管理系统设计与实现(丰富项目+远程调试+讲解+定制)
  • 异步SAR Simulink模型及其在MATLAB仿真中的应用
  • 【开题答辩全过程】以 基于Node.js的医院预约挂号系统为例,包含答辩的问题和答案
  • vue基于Spring Boot框架的在线电影票购买系统的设计与实现_8xxt52nn
  • 学完这个C++内存池案例,你对内存管理的理解将超越大部份人
  • Cplusplus生成代码大小的说明-–-behaviac