如果说Dijistra算法是通过点来对各个路径进行松弛的话,那么Bellman-ford是算法则是通过边来对进行松弛的。
即枚举每一条边,然后比较源点到该边的终点的估计最短路径估计值和源点到该边的起点的最短路径估计值加上该边的长度,
例如,已知源点到各个点的最短路径估计值dis[i]为 0 5 2 6 7,有一条边的信息为从点3到点4的长度为3,那么此时通过改变来进行松弛可以将dis[4] = 6松弛为2+3=5了,是不是感觉有些眉目了。
所以
for(j = 1; j <= m; ++j)
if(dis[v[j]] > dis[u[j]]+w[j])
dis[v[j]] = dis[u[j]]+w[j];
此时我们可以将松弛x轮的过程理解为最多通过x条边可以获得最短路径。
所以一个图中最多只能通过n-1条边获得最短路径
下面上代码,
#include <iostream>
#include <cstdio>
#define inf 10e7
using namespace std;
int u[105], v[105], w[105], dis[105], jk[105];
int main(){
int i, j, n, m, flag;
while(cin >> n >> m){
for(i = 1; i <= m; ++i)
cin >> u[i] >> v[i] >> w[i];
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
for(i = 1; i < n; ++i){
for(j = 1; j <= m; ++j)
if(dis[v[j]] > dis[u[j]]+w[j])
dis[v[j]] = dis[u[j]]+w[j];
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
}
return 0;
}
时间复杂度为O(MN),时间复杂度相比DIjistra算法似乎有些高,但实际上,Bellman-ford算法经常会在未达到n-1轮就已经计算出最短路径了,所以还可以优化,所以博主是很喜欢这个算法的。
#include <iostream>
#include <cstdio>
#define inf 10e7
using namespace std;
int u[105], v[105], w[105], dis[105], jk[105];
int main(){
int i, j, n, m, flag;
while(cin >> n >> m){
for(i = 1; i <= m; ++i)
cin >> u[i] >> v[i] >> w[i];
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
for(i = 1; i < n; ++i){
for(j = 1; j <= n; ++j)
jk[j] = dis[j];
for(j = 1; j <= m; ++j)
if(dis[v[j]] > dis[u[j]]+w[j])
dis[v[j]] = dis[u[j]]+w[j];
for(j = 1; j <= n; ++j)
if(jk[j] != dis[j]) break;
if(j == n+1) {
printf("%d\n", i);
break;
}
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
}
return 0;
}
在这儿我们可以体会到,在每次完成一轮松弛之后,便会有一些点已经求得最短路径,一会这些最短路径估计值便不会改变,不再受后面松弛的影响,但以后每次还是要判断是否松弛,浪费了时间。所以,每次仅对最短路径发生变化了的顶点的所有出边进行松弛操作。这就引出了我们的队列优化的Bellman-ford算法,又叫做SPFA算法。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
struct node1{
int v, w, next;
}edge[20005];
int n, m, no, head[105], dis[105], book[105], pre[105];
queue<int> q;
void add(int u, int v, int w){
edge[no].v = v;
edge[no].w = w;
edge[no].next = head[u];
head[u] = no++;
}
void init(){
no = 1;
memset(dis, 0x3f, sizeof dis);
memset(pre, -1, sizeof pre);
memset(book, 0, sizeof book);
memset(head, -1, sizeof head);
}
void SPFA(int u){
int k, x;
while(!q.empty()) q.pop();
dis[u] = 0, book[u] = 1;
q.push(u);
while(!q.empty()){
x = q.front();
q.pop();
book[x] = 0;
k = head[x];
while(k != -1){
if(dis[edge[k].v] > dis[x]+edge[k].w){
dis[edge[k].v] = dis[x]+edge[k].w;
pre[edge[k].v] = x;
if(book[edge[k].v] == 0){
book[edge[k].v] = 1;
q.push(edge[k].v);
}
}
k = edge[k].next;
}
}
}
void PrintPath(int end){ //打印路径
int k = end;
while(k != -1)
{
cout << k << " ";
k = pre[k];
}
cout << endl;
}
int main(){
ios::sync_with_stdio(0);
int i, u, v, w;
while(cin >> n >> m && (n || m)){
init();
for(i = 1; i <= m; ++i){
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
SPFA(1);
for(i = 1; i <= n; ++i) printf("%d ", dis[i]);
printf("\n");
PrintPath(5);
}
return 0;
}
/*
样例:
5 5
1 2 2
1 3 3
2 4 4
3 4 2
4 5 1
*/
已经介绍完所有最短路径的算法了,如果对Floyed-Warshell算法和DIjistra算法有兴趣的朋友可以看前两篇文章。