bellman-ford 单源最短路问题 图解
核心思想:松弛操作
对于边(u,v),用dist(u)和(u,v)的和尝试更新dist(v):
dist(v) = min(dist(v) , dist(u)+l(u,v)
注:dist(i)为源点(起点)到i点的距离,l(u,v)为u->v的边权。
Bellman-Ford的基本操作是进行多次迭代,每一轮迭代对图上所有边进行松弛操作,直到再一次迭代中没有点的dist发生变化即可停止迭代。为什么呢?不妨假设已经没有dist发生变化了,再进行一轮迭代的话,很显然,之后的迭代没有产生任何作用,dist数组依旧没有改变,反倒增大了时间复杂度,这不是多此一举么。
图解:

初始:(S为源点)
初始设置为inf无穷大,表示还没有最短路
| S | A | B | C | D | E |
| 0 | inf | inf | inf | inf | inf |
第一轮迭代:
对S点连出的边(s->e,s->a)
| S | A | B | C | D | E |
| 0 | 7(0+7) | inf | inf | inf | 5(0+5) |
对A连出的边(a->c)
| S | A | B | C | D | E |
| 0 | 7 | inf | 9(7+2) | inf | 5 |
对B连出的边(b->a)
| S | A | B | C | D | E |
| 0 | 7 | inf | 9 | inf | 5 |
dist(B)还没有找到最短路,更新其他点的最短路径无意义,故对B点的出边不进行松弛
对C连出的边(c->b)
| S | A | B | C | D | E |
| 0 | 7 | 7(9+(-2)) | 9 | inf | 5 |
对D连出的边(d->c,d->a)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | inf | 5 |
dist(D)还没有找到最短路,更新其他点的最短路径无意义,故对D点的出边不进行松弛
对E连出的边(e->d)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | 6(5+1) | 5 |
已经对所有的边进行了松弛操作,第一轮迭代结束
第二轮迭代
对S点连出的边(s->e,s->a)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | 6 | 5 |
无需更新
对A连出的边(a->c)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | 6 | 5 |
无需更新
对B连出的边(b->a)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | 6 | 5 |
无需更新
对C连出的边(c->b)
| S | A | B | C | D | E |
| 0 | 7 | 7 | 9 | 6 | 5 |
无需更新
对D连出的边(d->c,d->a)
| S | A | B | C | D | E |
| 0 | 2(6+(-4)) | 7 | 5(6+(-1)) | 6 | 5 |
对E连出的边(e->d)
| S | A | B | C | D | E |
| 0 | 2 | 7 | 5 | 6 | 5 |
已经对所有的边进行了松弛操作,第二轮迭代结束
第三轮迭代
与第一第二轮同理(此处直接给出迭代结束的结果)
| S | A | B | C | D | E |
| 0 | 2 | 2 | 4 | 6 | 5 |
第四轮迭代
无任何更新,迭代结束,更新完成
算法分析:
如果最短路存在,一定存在一个不含环的最短路。(理由:对零环和正环,去掉后路径不会边长;对负环,若最短路径中存在负环,那一定不是最短路,负环可以无限绕下去,路径可以是负无穷)
最短路不含环,那么一条最短路径最多经过n-1个点(不含起点),所以最多需要n-1轮松弛操作。
复杂度分析:
最多进行n-1次迭代,每次迭代枚举遍历所有边,尝试通过边进行松弛操作,故复杂度为
O(N-1)*O(M)即O(NM),(注:N为点数,M为边数)
伪代码
for (int i = 0; i <= n; i++)
dist[i] = inf;//初始化为无穷大
dist[s] = 0;//s为起点,自己到自己的最短路为0
for (int k = 1; k <= n - 1; k++)//迭代n-1轮
{
for (int i = 1; i <= m; i++)//枚举每一条边
{
int x = u[i], y = v[i];
if (dist[x] < inf)
dist[y] = min(dist[y], dist[x] + w[i]);//松弛
}
}
检查有无负环
将dist数组初始化为0,迭代n-1次后进行第n次迭代,如果第n次迭代有进行松弛操作,则一定存在负环,因为不存在负环最多只能进行n-1次松弛操作
代码实现:
void bellman_ford(int s, int end) // s为起点,end为终点
{
memset(dis, 127, sizeof(dis));
dis[s] = 0; //起点最短路为0
pre[s] = -1;
for (int i = 1; i <= n - 1; i++)
{
bool ok = false;
for (int j = 1; j <= m; j++)
{
int x = edge[j].u, y = edge[j].v, w = edge[j].w;
if (dis[x] < (1 << 30) && dis[x] + w < dis[y])
{
dis[y] = dis[x] + w;
pre[y] = x; // y的上一个点为x,如不需打印路径无需pre数组
ok = true;
}
}
if (ok == false)
{
break; //未进行松弛操作,提前退出循环,减小时间复杂度
}
}
if (dis[end] < (1 << 30))
cout << dis[end] << "\n";
else
cout << "-1\n";
// Print_Path(end); //打印路径
}
模板题
题目链接:最短路 - 题目 - Daimayuan Online Judge
题目描述:
给你一张简单有向图,边权都为非负整数。以及一些询问,询问两个点之间的距离。
图用以下形式给出:
第一行输入三个整数 n,m,k表示图的顶点数、边数和询问次数,顶点编号从 1 到 n。
接下来 m 行,每行三个整数 x,y,z表示 x 到 y 有一条有向边,边权为 z。
接下来 k 行,每行两个整数 x,y 询问从 x 到 y 的最短路长度,如果无法到达,输出 −1。
输入格式:
第一行三个整数 n,m,k 表示图的顶点数、边数和询问次数。
接下来 m 行,每行有三个整数,代表一条边。
接下来 k 行,每行有两个整数,代表一次询问。
输出格式:
输出共 k 行,每行一个数表示一次询问的答案。
数据规模:
对于所有数据,保证 2≤n≤5000,0≤m≤10000,1≤k≤5,1≤x,y≤n,x≠y,1≤z≤10000。
样例输入:
3 3 2
1 2 3
2 3 2
3 2 1
1 3
3 1
样例输出:
5
-1
直接给代码了
#include <bits/stdc++.h>
using namespace std;
struct Edge
{
int u, v, w;
} edge[100009];
int pre[100009]; //记录上一个点,为了打印最短路径
int dis[100009], n, m, k; // n为点数,m为边数,dis[i]为起点到i的最短距离
void Print_Path(int x)
{
if (pre[x] == -1)
{
cout << x; //起点的pre为-1,所以x为起点
return;
}
else
{
Print_Path(pre[x]);
cout << "->" << x;
}
}
void bellman_ford(int s, int end) // s为起点,end为终点
{
memset(dis, 127, sizeof(dis));
dis[s] = 0; //起点最短路为0
pre[s] = -1;
for (int i = 1; i <= n - 1; i++)
{
bool ok = false;
for (int j = 1; j <= m; j++)
{
int x = edge[j].u, y = edge[j].v, w = edge[j].w;
if (dis[x] < (1 << 30) && dis[x] + w < dis[y])
{
dis[y] = dis[x] + w;
pre[y] = x; // y的上一个点为x
ok = true;
}
}
if (ok == false)
{
break; //未进行松弛操作,提前退出循环,减小时间复杂度
}
}
if (dis[end] < (1 << 30))
cout << dis[end] << "\n";
else
cout << "-1\n";
// Print_Path(end); //打印路径
}
int main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //关同步流
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) //读入边
{
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
for (int i = 1; i <= k; i++) // k次询问
{
int x, y;
cin >> x >> y;
bellman_ford(x, y);
}
}

参考文献:
《算法竞赛,入门经典(第二版)》
2022 Namomo Spring Camp Div2 Day8 直播课
ending
有什么错误之处欢迎指正!不胜感激!
bellman-ford 单源最短路问题 图解的更多相关文章
- 单源最短路问题:OJ5——低德地图
本题就是一道单源最短路问题.由于是稀疏图,我们采用Dijkstra算法. Dijkstra算法原理 Dijkstra算法的步骤 我们把所有的节点分为两个集合:被选中的(visited==1) 和 未被 ...
- dijkstra算法解决单源最短路问题
简介 最近这段时间刚好做了最短路问题的算法报告,因此对dijkstra算法也有了更深的理解,下面和大家分享一下我的学习过程. 前言 呃呃呃,听起来也没那么难,其实,真的没那么难,只要弄清楚思路就很容易 ...
- 【模板】Bellman—Fort 单源最短路径算法
2333 适用于边集储存 #include<bits/stdc++.h> using namespace std; const int inf=0x3fffffff; ],t[],d[], ...
- Bellman-Ford算法解决单源最短路问题
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define max 100 #define I ...
- hdu 2544 单源最短路问题 dijkstra+堆优化模板
最短路 Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- 单源最短路问题--朴素Dijkstra & 堆优化Dijkstra
许久没有写博客,更新一下~ Dijkstra两种典型写法 1. 朴素Dijkstra 时间复杂度O(N^2) 适用:稠密图(点较少,分布密集) #include <cstdi ...
- 单源最短路问题 Dijkstra 算法(朴素+堆)
选择某一个点开始,每次去找这个点的最短边,然后再从这个开始不断迭代,更新距离. 代码: 朴素(vector存图) #include <iostream> #include <cstd ...
- 单源最短路——Bellman-Ford算法
1.Dijkstra的局限性 Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的. 列如以 ...
- [ACM_图论] Domino Effect (POJ1135 Dijkstra算法 SSSP 单源最短路算法 中等 模板)
Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...
随机推荐
- vue的seo问题?
seo关系到网站排名, vue搭建spa做前后端分离不好做seo, 可通过其他方法解决: SSR服务端渲染: 将同一个组件渲染为服务器端的 HTML 字符串.利于seo且更快. vue-meta-in ...
- 简述synchronized和java.util.concurrent.locks.Lock的异同 ?
主要相同点:Lock能完成synchronized所实现的所有功能 主要不同点:Lock有比synchronized更精确的线程语义和更好的性能.synchronized会自动释放锁,而Lock一定要 ...
- 11_二阶系统的单位阶跃响应_详细数学推导部分_2nd order system unit step response
- JavaScript HTML5脚本编程——“历史状态管理”的注意要点
历史状态管理是现代Web应用开发中的一个难点.在现代Web应用中,用户的每次操作不一定会打开一个全新的页面,因此"后退"和"前进"按钮也就失去了作用,导致用户很 ...
- WePY为了兼容支付宝小程序,改了好几十行代码
早在16年底,就有流出支付宝在做小程序的事情,见<如何看待支付宝推出「小程序」?>,今年8月18号支付宝版本小程序的终于公测,十月怀胎实属不易啊. 紧接着就有人给我提ISSUE了: 此时我 ...
- ES6-11学习笔记--Promise
Promise是ES6异步编程解决方案之一,简化以前ajax的嵌套地狱,增加代码可读性. 基本用法: resolve,成功 reject,失败 let p = new Promise((resol ...
- python-输入列表,求列表元素和(eval输入应用)
在一行中输入列表,输出列表元素的和. 输入格式: 一行中输入列表. 输出格式: 在一行中输出列表元素的和. 输入样例: [3,8,-5] 输出样例: 6 代码: a = eval(input()) t ...
- mysql数据乱码
更改数据库安装时的字符编码.打开mysql安装目录,找到my.ini文件,通过使用记事本的方式打开,将这里面的default-character-set=latin1修改成gbk,注意这里面有两处需要 ...
- 记一次修改框架源码的经历,修改redux使得redux 可以一次处理多个action,并且只发出一次订阅消息
redux是一个数据状态管理的js框架,redux把行为抽象成一个对象,把状态抽象成一个很大的数据结构,每次用户或者其他什么方式需要改变页面都可以理解成对数据状态的改变,根据出发这次改变的不同从而有各 ...
- HTML5有哪些更新(部分)
1. 语义化标签 header:定义文档的页眉(头部): nav:定义导航链接的部分: footer:定义文档或节的页脚(底部): article:定义文章内容: section:定义文档中的节(se ...