最短路算法 - Bellman-Ford

在了解该算法之前,可以去了解迪杰特斯拉算法,然后再回来看这个。

我们都知道迪杰特斯拉求解最短路的时候,图中是不能存在负权边的,因为如果存在负权边的话,迪杰斯塔拉可能不能得到正确的结果。

比如下面这种情况,起点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;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值