在了解该算法之前,可以去了解迪杰特斯拉算法,然后再回来看这个。
我们都知道迪杰特斯拉求解最短路的时候,图中是不能存在负权边的,因为如果存在负权边的话,迪杰斯塔拉可能不能得到正确的结果。
比如下面这种情况,起点1,终点4
这种情况在迪杰斯特拉中会先求出1,3、然后求出3,4、再求出1,2。这很明显就出现了问题,到4的最短路我们很容易看出权值和应该为-5,但是用迪杰斯特拉求出来变成了3。因为迪杰斯特拉在设计的时候就没有去处理负权边的问题,他的这种算法思想就是建立在图中的边全部为正数的情况之下。因为如果全部都是正数的话,那么我们从源节点开始寻找的时候,找到的最小权值的边,然后根据该边确定一个点的最短路径,为什么能确定该点?因为如果所有边都是正数的情况下,就能确定当前选择的这个边是到达目的点上的最小的边,不会有负边来使其变小。
Bellman-ford
该算法就可以用于求解带有负权边的图,且还可以检查出带有负权环的图。如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路。这会存在什么问题呢,权值之和是负数,就可能会使环路上的最短路越来越小,怎么使他越来越小,就是一直在环上转圈圈,而且这种情况下求最短路径本身来说就是没有什么意义的,因为最短路可以一直变小,Bellman-ford可以告诉你该图有没有意义。
所以Bellman-ford算法就能解决这两个问题
1.图中可以带负权边。
2.找出该图有没有求最短路的意义。
算法正确性证明:
松弛操作:即利用已知的点之间的距离来参试缩短其他点直接的距离
假设s到v的最短真实距离是v.r
有如下引理:
假如s->vk的最短路径为p=<v0,v1,v2,...vk>,那么我们对p中的边所进行的松弛顺序为(v0,v1),(v1,v2),...,(Vk-1,Vk),这样就确定了该点的最短路。该性质的成立与其他任何松弛操作无关,即使这些松弛操作是与对p上的边所进行的松弛操作穿插进行的。
你可能说松弛操作从K往1这种顺序进行松弛怎么办,说这话你可能没有理解上面的描述,其实完全没有必要有这种考虑,因为我们每一次的内层循环,会对所有的边进行松弛操作,这样的操作,也就能得到我们上面的松弛顺序,就是说每次必定能从0这个边往后找。
有了以上引理,第i轮循环就可以看做是对(vi-1,vi)进行松弛。所以经过|V|-1轮后,对于所有v∈V,都有v.d=v.r
所以可以得到s到所有顶点的最短距离。
#include <stdio.h>
#include <stdlib.h>
#define MAX 10000
#define M 0x7f7f7f
// 有向边,这里没有使用什么邻接表或者邻接矩阵,因为本来就要遍历所有的点,这个操作完全没有必要
typedef struct{
int s;
int e;
int w;
}Edge;
Edge edges[MAX];
int dist[MAX];
int v,e;
int main()
{
// 初始化的时候源点到每一个点的代价都是最大值
memset(dist,127,sizeof(dist));
// 源点到源点的代价为0
dist[1] = 0;
scanf("%d%d",&v,&e);
// 输入边,注意是输入的有向边这里,如果是无向边需要一点修改
for(int i=0;i<e;i++){
scanf("%d%d%d",&edges[i].s,&edges[i].e,&edges[i].w);
}
int res = bellmanFord();
if(!res){
for(int i=1;i<=v;i++){
printf("%d ",dist[i]);
}
}else{
printf("error\n");
}
return 0;
}
int bellmanFord(){
// 每一次外层循环都能确定一个点的最短路径,然后利用该点去解决其他最短路径
// 这一点与迪杰斯特拉的思想就很相似了。
// 如果在图中不包含负权图,那么就能正确的求出每个点的最短路
// 但是包含,那么每一次循环都会使一些点的最短路变小
// 如果觉得不清晰可以自行画图
for(int i=1;i<v;i++){
// 内层循环遍历所有的边
for(int j=0;j<e;j++){
relax(edges[j].s,edges[j].e,edges[j].w);
}
}
// 标识最短路径是否有意义
int flag = 0;
// 如果还有边能使最短路变小,一定存在负权环
for(int i=0;i<e;i++){
if(dist[edges[i].e] > dist[edges[i].s] + edges[i].w){
flag = 1;
break;
}
}
return flag;
}
// 松弛操作
void relax(int s,int e,int w){
if(dist[e] > dist[s] + w){
dist[e] = dist[s] + w;
}
}