Bellman-ford(单源最短路)
基本原理:
不断尝试对图上每一条边进行松弛。我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。
特点:
边权没有要求,可正可负,还可判断负权边.时间复杂度为O(nm),但是经过队列优化,时间复杂度是<=O(nm)。
算法思想:
图的任意一个条最短路,既不会包含负权回路,也不会包含正权回路,最多包含n-1边。那么,从源点s开始,可以达到的节点,如果存在最短路,则最短路构成了一颗以s为根的最短路树。因此,可以按照距离根s的层次,逐层生成达到每个点的最短路(松弛操作);所以整个过程,就是创建最短路树的过程;需要一个辅助数组d[n]和v[n]来记录最短路距离和跟踪寻迹;从边的角度来考虑,每次迭代要遍历每条边;循环n-1次后,第n次循环如果所有d[n]值不更新,则跳出循环;如果第n次还存在路径更新,则说明存在负环;Bellman-Ford算法也可以求解最长路和用来判断正环,只要在递推关系选择最大的更新就好。
Bellman-Ford算法还能用来求最长路或者判断正环。
Bellman-ford与Dijkstra算法的区别:
迪杰斯特拉(Dijkstra)算法是借助贪心思想,每次选取一个未处理的最近的结点,去对与他相连接的边进行松弛操作;贝尔曼福特(Bellman-ford)算法是直接对所有边进行N-1遍松弛操作。
迪杰斯特拉(Dijkstra)算法要求边的权值不能是负数;贝尔曼福特(Bellman-ford)算法边的权值可以为负数,并可检测负权回路。
void bellman_ford(int s)
{
memset(dis,MM,sizeof(dis));
dis[s] = 0;
bool change;
for (int i = 0; i < n - 1; i++)
{
change = false;
for (int k = 0; k < m; k++)
{
if (dis[edge[k].to] > dis[edge[k].from] + edge[k].value)
{
change = true;
dis[edge[k].to] = dis[edge[k].from] + edge[k].value;
patch[edge[k].to]=edge[k].from;
}
}
if (change==false)
{
break;
}
}
}
Bellman-Ford算法可以大致分为三个部分:
初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
遍历途中所有的边(edge(u,v)),判断是否存在这样情况: d(v) > d (u) + w(u,v),若存在,则返回false,表示图中存在从源点可达的权为负的回路。
之所以需要第三步的原因,是因为,如果存在从源点可达的权为负的回路,则将因为无法收敛而导致不能求出最短路径。
SPFA(Bellman-ford的队列优化版)
基本原理:
bellman-ford中有很多重复无用的松弛操作,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。所以,我们用队列来储存被松弛的结点,就可以访问必须访问的边了。
特点:
边权没有要求,可正可负,还可判断负权边(如果没有负边权,推荐使用Dijkstra算法)。时间复杂度大多数情况情况快,最坏可达O(nm)。(与bfs算法比较,复杂度相对稳定)
SPFA算法有四个优化策略:堆优化、栈优化、SLF和LLL
- 堆优化:将队列换成堆,与 Dijkstra 的区别是允许一个点多次入堆。在有负权边的图可能被卡成指数级复杂度。
- 栈优化:将队列换成栈(即将原来的 BFS 过程变成 DFS),在寻找负环时可能具有更高效率,但最坏时间复杂度仍然为指数级。
- SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾;
- LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。
SLF 和 LLL 优化在随机数据上表现优秀,但是在正权图上最坏情况为 O(VE),在负权图上最坏情况为达到指数级复杂度。
struct edge {
int v, w;
};
vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;
bool spfa(int n, int s) {
memset(dis, 63, sizeof(dis));
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1; // 记录最短路经过的边数
if (cnt[v] >= n) return false;
// 在不经过负环的情况下,最短路至多经过 n - 1 条边
// 因此如果经过了多于 n 条边,一定说明经过了负环
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return true;
}
题目详情: 洛谷P3371 【模板】单源最短路径(弱化版)
如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
输入格式:
第一行包含三个整数
n
,
m
,
s
n,m,s
n,m,s,分别表示点的个数、有向边的个数、出发点的编号。
接下来
m
m
m 行每行包含三个整数
u
,
v
,
w
u,v,w
u,v,w,表示一条
u
→
v
u \to v
u→v 的,长度为
w
w
w 的边。
输出格式:
输出一行 n n n 个整数,第 i i i 个表示 s s s 到第 i i i 个点的最短路径,若不能到达则输出 2 31 − 1 2^{31}-1 231−1。
提示:
【数据范围】
对于
20
%
20\%
20% 的数据:
1
≤
n
≤
5
1\le n \le 5
1≤n≤5,
1
≤
m
≤
15
1\le m \le 15
1≤m≤15;
对于
40
%
40\%
40% 的数据:
1
≤
n
≤
100
1\le n \le 100
1≤n≤100,
1
≤
m
≤
1
0
4
1\le m \le 10^4
1≤m≤104;
对于
70
%
70\%
70% 的数据:
1
≤
n
≤
1000
1\le n \le 1000
1≤n≤1000,
1
≤
m
≤
1
0
5
1\le m \le 10^5
1≤m≤105;
对于
100
%
100\%
100% 的数据:
1
≤
n
≤
1
0
4
1 \le n \le 10^4
1≤n≤104,
1
≤
m
≤
5
×
1
0
5
1\le m \le 5\times 10^5
1≤m≤5×105,
1
≤
u
,
v
≤
n
1\le u,v\le n
1≤u,v≤n,
w
≥
0
w\ge 0
w≥0,
∑
w
<
2
31
\sum w< 2^{31}
∑w<231,保证数据随机。
#include<bits/stdc++.h>
using namespace std;
const int NR=100000;
int d[NR],n,m,s; // d[NR]:最短路径存储列表
vector< pair<int,int> > adj[NR];//一个结构体队列
void spfa(int s){//spfa函数
memset(d,0x3f,sizeof(d));
queue<int> q;
q.push(s);d[s]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<adj[u].size();i++){
int v=adj[u][i].first;
int w=adj[u][i].second;
if(d[u]+w<d[v]){
d[v]=d[u]+w;
q.push(v);
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=0;i<m;i++){
int st,en,w;
scanf("%d%d%d",&st,&en,&w);
adj[st].push_back(make_pair(en,w));
}
spfa(s);
for(int i=1;i<=n;i++){//输出
if(d[i]>=1e9)printf("2147483647 ");
else printf("%d ",d[i]);
}
return 0;
}
时间复杂度分析:
- Floyed算法:求多源最短路,可以处理负边;时间复杂度为O(n3);
- Dijkstra算法:求单源最短路,不能处理负边;时间复杂度为O(n2);
- Bellman-Ford算法:求单源最短路,可以处理负权边;时间复杂度为O(NM);
- SPFA算法:求单源最短路,Bellman-ford算法优化版本,可以处理负权边;时间复杂度为O(kM)~O(NM); k为较小常数;