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

代码随想录第59天 | 最短路算法:dijkstra和Bellman_ford

目标:

了解原理,能照着代码写出来,理解即可。

最短路:

最短路是图论中的经典问题——

给出一个有向图,一个起点,一个终点,问起点到终点的最短路径

而求最短路径的算法是——

dijkstra算法:在有权图(权值非负数的基础上)中求从起点到其他节点的最短路径的算法

dijkstra朴素版

这个算法其实和prim算法很类似,都是贪心的思路,每次都找最近的节点。

下面是dijkstra算法的三部曲:

1. 选源点(开始节点)到哪个节点且该节点未被访问

2. 把这个最近节点(刚被选择过的)标记为——访问过

3. 更新非访问节点到源点的距离(即更新minDist数组)——这个更新的是谁与刚才选择的节点连接了,则谁就更新(更新是与之前的minDist元素比较,谁小选谁)

直到所有节点都被访问过,则说明到达终点。

minDist数组 用来记录 每一个节点距离源点(起始点)的最小路径距离

visited数组 用来记录每个节点是否被访问过

代码:

#include <iostream>
#include <vector>
#include <climits>//用于整型数据类型的极限值using namespace std;int main() {int n,m,p1,p2,val;cin>>n>>m;vector<vector<int>> grid(n + 1, vector<int>(n + 1, INT_MAX));//表示图,使用邻接矩阵的方式,因为边和点数量差不多for(int i=0;i<m;i++){cin>>p1>>p2>>val;grid[p1][p2] = val;}int start = 1;int end = n;//表示开始节点(源点)和结束节点//两个重要的数组,分别记录每个节点到源点的最短举例,每个节点是否被访问过vector<int> minDist(n + 1, INT_MAX);vector<bool> visited(n + 1, false);minDist[start] = 0;  // 起始点到自身的距离为0,其余全设置为maxfor (int i=1;i<=n;i++) { // 遍历所有节点int minval = INT_MAX;//用于记录到源点最短的距离int cur = 1;//记录当前被访问的节点//步骤1,选择未被访问且最近的for (int v=1;v<=n;++v) {if (!visited[v] && minDist[v] < minval) {minval = minDist[v];cur = v;}}visited[cur] = true;  // 步骤2,标记该节点为已访问// 步骤3,更新非访问节点(与cur连接的节点)到源点的距离(即更新minDist数组),更新需要是新的值与原来的值比较。for (int v = 1; v <= n; v++) {if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) {minDist[v] = minDist[cur] + grid[cur][v];}}}if (minDist[end] == INT_MAX) cout << -1 << endl; // 不能到达终点else cout << minDist[end] << endl; //输出结果}

dijkstra和prim算法的区别在于:minDist数组的含义不同,一个表示到达源点的最小路径距离,一个表示到达生成树的最小路径

其次prim可以用于负值的权值,且不涉及单一路径。

dijkstra(堆优化版)精讲

dijkstra(堆优化版)精讲

朴素版时间复杂度:O(n²)

  • 当 n = 10⁵ 时,n² = 10¹⁰,会超时

  • 主要瓶颈:每次都要遍历所有节点找最小值

那优化就是:用堆来快速完成"选择最近节点"这一步

1. 选择邻接表而非邻接矩阵

2. 两个数组minDist和visited不变

3. 引入优先队列——什么是优先队列呢?

priority_queue<PII, vector<PII>, greater<PII>> pq;
pq.push({0, start}); // {距离, 节点}

优先队列(小顶堆)是——自动按从小到大排序,总是取出最小的

优化三部曲:

while 循环开始(对应朴素版的 for 循环)用于遍历选择

步骤1:选择最近的未访问节点
auto [minDist, cur] = pq.top();  // 堆顶就是当前最小距离节点
pq.pop();if (visited[cur]) continue;  // 如果已经访问过就跳过

对比朴素版

  • 朴素版:遍历n个节点找最小值 → O(n)

  • 堆优化:直接取堆顶 → O(1)

步骤2:标记节点为已访问
visited[cur] = true;  // 完全相同!
步骤3:更新邻接节点的距离
for (auto [neighbor, weight] : graph[cur]) {if (!visited[neighbor]) {int newDist = dist[cur] + weight;if (newDist < dist[neighbor]) {dist[neighbor] = newDist;pq.push({newDist, neighbor}); // 关键:把更新后的节点加入堆}}
}

对比朴素版

  • 朴素版:遍历所有n个节点

  • 堆优化:只遍历当前节点的邻居(更高效)

示例

  • 节点3第一次加入堆:距离=5

  • 后来发现更短路径,节点3再次加入堆:距离=3

  • 当从堆中取出距离=3的节点3时,标记为已访问

  • 之后如果取出距离=5的节点3,直接跳过(因为已经有更优解)

代码

#include <iostream>
#include <vector>
#include <queue>
#include <climits>using namespace std;typedef pair<int, int> PII; // {节点编号, 距离}int main() {int n, m, p1, p2, val;cin >> n >> m;// 邻接表存储:graph[p1] = {邻居节点, 权重}vector<vector<PII>> graph(n + 1);for (int i = 0; i < m; i++) {cin >> p1 >> p2 >> val;graph[p1].push_back({p2, val}); // {邻居节点, 权重}}int start = 1, end = n;vector<int> dist(n + 1, INT_MAX);vector<bool> visited(n + 1, false);// 优先队列:{距离, 节点} ← 注意:这里要按距离排序,所以距离放前面!!priority_queue<PII, vector<PII>, greater<PII>> pq;dist[start] = 0;pq.push({0, start}); // {距离, 节点} ← 修正这里!while (!pq.empty()) {// 步骤1:取堆顶auto [minDist, cur] = pq.top(); // 现在第一个是距离,第二个是节点pq.pop();if (visited[cur]) continue;// 步骤2:标记为已访问visited[cur] = true;// 步骤3:更新邻居for (auto [neighbor, weight] : graph[cur]) { // {邻居节点, 权重}if (!visited[neighbor]) {int newDist = dist[cur] + weight;if (newDist < dist[neighbor]) {dist[neighbor] = newDist;pq.push({newDist, neighbor}); // {距离, 节点}}}}}if (dist[end] == INT_MAX) cout << -1 << endl;else cout << dist[end] << endl;
}

Bellman_ford 算法精讲(权值为负的最短路径

Bellman_ford 算法精讲

这个算法解决:权值存在负时的单源最短路问题

思路是:对所有边进行松弛n-1次操作(n为节点数量),从而求得目标最短路

基本概念:松弛操作

什么是松弛操作呢?对于节点v而言,有多个路径可以到达,若从u节点通过权值为weight的边到达v节点,则其dist需要和v节点之前的dist比较,谁更小选择谁。

//核心操作
if (dist[u] + weight < dist[v]) {dist[v] = dist[u] + weight;
}

意思:如果通过u到达v比当前已知的到v的路径更短,就更新

松弛操作的注意事项:

对所有的边进行n-1轮松弛操作(n为节点数)

  • 第一轮松弛后确定了从起点出发,经过最多1条边能到达的所有节点的最短距离

  • 第二轮松弛后:确定了从起点出发,经过最多2条边能到达的所有节点的最短距离

  • 第k轮松弛后:确定了从起点出发,经过最多k条边能到达的所有节点的最短距离

示例:

1 → 2 (权重1)
1 → 4 (权重10)
2 → 3 (权重1)  
3 → 4 (权重1)

第1轮松弛:

1→2: dist[2] = 1
1→4: dist[4] = 10
2→3: dist[3] = 2
3→4: 还不能更新,因为dist[3]还没确定

第1轮后:dist = [0, 1, 2, 10]

第2轮松弛:

3→4: dist[3]+1=3 < 10 → dist[4] = 3  ← 发现更短路径!

第2轮后:dist = [0, 1, 2, 3]

看!节点4在第1轮是10,第2轮才找到更优的3

——⭐⭐第一轮找到的是从开始节点出发,只经过1条边,到达所有节点的最短距离⭐⭐

⭐则从上推测——需要n-1轮来确认从开始节点到达所有节点的最短路径(无论经过多少边)

  • 在最坏情况下,最短路径最多包含n-1条边

  • 每轮松弛至少确定一个节点的最短路径

算法详细步骤

第一步:初始化

vector<int> dist(n + 1, INT_MAX);
dist[start] = 0;  // 起点到自己的距离为0

第二步:进行n-1轮松弛

for (int i = 1; i <= n - 1; i++) {for (每条边(u, v, weight)) {if (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {dist[v] = dist[u] + weight;}}
}

第三步:检测负权环(可选)

for (每条边(u, v, weight)) {if (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {// 存在负权环!}
}

完整代码实现

#include <iostream>
#include <vector>
#include <climits>using namespace std;struct Edge {int from;int to;int weight;
};int main() {int n, m;cin >> n >> m;vector<Edge> edges;for (int i = 0; i < m; i++) {int u, v, w;cin >> u >> v >> w;edges.push_back({u, v, w});}int start = 1, end = n;vector<int> dist(n + 1, INT_MAX);dist[start] = 0;// 进行n-1轮松弛for (int i = 1; i <= n - 1; i++) {//n-1轮for (auto& edge : edges) {//遍历边!!!!int u = edge.from;//从uint v = edge.to;//到vint w = edge.weight;//权值为w// 松弛操作if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {//更新v为最小的,且前提是u不算maxdist[v] = dist[u] + w;}}}if (dist[end] == INT_MAX) {cout << "unconnected" << endl;  // 不可达} else {cout << dist[end] << endl;}
}

实例演示

示例1:正常情况

输入:
4 5
1 2 2
1 3 4
2 3 1
2 4 7
3 4 3起点:1,终点:4

执行过程

  • 初始化:dist = [0, max, max, max]

  • 第1轮松弛后:dist = [0, 2, 4, max]

  • 第2轮松弛后:dist = [0, 2, 3, 6] (通过2->3更新了3)

  • 第3轮松弛后:dist = [0, 2, 3, 6] (无变化)

结果:6

示例2:含负权边

输入:
3 3
1 2 3
1 3 5
2 3 -2起点:1,终点:3

执行过程

  • 初始化:dist = [0, max, max]

  • 第1轮:dist = [0, 3, 5]

  • 第2轮:通过2->3(-2)更新:dist = [0, 3, 1]

结果:1(比直接1->3的5更短)

有关生成树和最小路径算法选择指南

根据图类型选择:

稠密图(边数 ≈ n²)

  • 使用朴素版 Dijkstra

  • 时间复杂度:O(n²)

稀疏图(边数 ≈ n)

  • 使用堆优化版 Dijkstra

  • 时间复杂度:O((n+m)logn)

根据问题需求选择:

单源最短路径

  • 正权图:Dijkstra(朴素/堆优化)

  • 负权图:Bellman-Ford / SPFA

所有点对最短路径

  • Floyd 算法

最小生成树

  • 稠密图:Prim

  • 稀疏图:Kruskal

http://www.dtcms.com/a/573126.html

相关文章:

  • web自动化测试详解
  • 网站建设文章官网小程序定制开发中心
  • PortSwigger靶场之利用开放重定向漏洞绕过过滤器的 SSRF 攻击通关秘籍
  • 深入理解 Spring 原理:IOC、AOP 与事务管理
  • 做网站公司赚钱吗怎么怎么做网站
  • 使用ESP8266+SG90舵机实现物理远程开机
  • 第四阶段C#通讯开发-5:TCP
  • WABT 项目全解析:WebAssembly 二进制工具套件
  • 第四阶段C#通讯开发-5:Socket与RS485 / Modbus联调
  • 辽宁建设资质申报网站国外直播sdk
  • 适配的 GPU 服务器能让 AI 模型充分发挥算力优势
  • 【高并发服务器:HTTP应用】十五、HttpRequest请求模块 HttpResponse响应模块设计
  • 两台服务器 NFS 共享目录实战
  • 在服务器已有目录中部署 Git 仓库
  • 宝塔Linux部署 一个基于uni-app 系统指南
  • PostgreSQL 索引
  • Ubuntu20.04配置使用evo工具
  • 远程连接银河麒麟服务器-xrdp方式
  • 做阿里巴巴网站图片大全wordpress添加固定结尾
  • k8s集群搭建(七)-------- 微服务间的调用
  • 华为HCIP网络工程师认证—网络参考模型
  • 商务网站建设策略网站设计 原型图
  • 告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
  • 21、【Ubuntu】【远程开发】技术方案分析:虚拟专用网络(补充)
  • 中山网站制作建设通讯员队伍建设与网站新闻管理
  • 论文阅读:《Hypergraph Motif Representation Learning》
  • 快速了解搭建网站流程——全栈网站搭建指南
  • pdf图片提取器pyqt6版本实现
  • 基于Layui Vue Admin + Spring Boot 3.x 的企业级前后端分离管理系统
  • Apache SeaTunnel 支持 Metalake 开发了!避免任务配置敏感信息暴露