Fork me on GitHub

最短路径算法专题3----Bellman-Ford

这个算法也是求单源最短路径用的,但是这个算法可以解决Dijkstra不能解决的负权边问题。

 

算法要点:

1、用dis存放源点到任意一点的距离。

2、用三个数组存放输入的点到点以及点到点的距离,x【i】,y【i】,x_y_dis【i】其中表示x【i】到y【i】的距离为x_y_dis【i】。

3、循环边数,比较dis【y【i】】和dis【x【i】】+ x_y_dis【i】,然后更新dis【y【i】】,意思和Dijkstra很像,就是源点到y点的距离如果大于源点到x的距离加上x到y的距离就更新。//注意这里是y在dis前面,因为y是目的点,x到y是有方向的。

4、最外面循环点数,注意要循环N-1个点,因为N个点最短距离的最大边数不能超过N-1。

5、设置两个标识去标志,已经不能松弛了,尽早跳出循环,图有负权环路(只是进行优化处理,在模型中就不写出了)

 

算法模型:

for(点数-1)

{

        for(边数)

        {

              if(dis【y【i】】> dis【x【i】】+ x_y_dis【i】)

                 dis【y【i】】= dis【x【i】】+ x_y_dis【i】)

        }

}

 

算法的核心:

最外面循环第一次意思是,找到源点到目的地,经过一条边也就是进过输入时的纯x_y_dis距离比现在的距离小,那么进行松弛。

第二次循环就是,经过两条边之后的优化,因为经过一条边的最小值已经在第一次循环保存进dis中了,所以是经过两条边。

这也就很好解释为啥是N-1次循环,因为如果N个点,有N条边那一定肯定其中有回路了。

也就很好解释了为什么标识能过提前跳出循环,因为如M边的循环不能松弛的话,那么加上M+1条边就不可能再松弛了。

 

#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace std;
/*Bellman-Ford*/

int dis[100];
int x[100];
int y[100];
int x_y_dis[100];
const int MAX = 99999;//定义一个最大值,距离不会超过这个

int main()
{
    int i,j,q;//循环变量
    int n,m;
    int check=0;//当这个标记为1时代表重复了,已经不能松弛了,尽早跳出循环
    int flag=0;//当这个标记为1时代表这个图有负权环路
    cin>>n>>m;//输入N*N的图,和M条边对应的值

    for (i = 1; i <= n; i++)//初始化dis距离
        dis[i] = MAX;

    for (i = 1; i <= m; i++)
    {
        cin>>x[i]>>y[i]>>x_y_dis[i];//输入x到y的距离为z
    }

    dis[1] = 0;//自己到自己肯定是0
    
    for (q = 1; q <= n-1; q++)//注意最外面的循环是循环的总的点数减一个,因为两个点最多包含N-1条边
    {
        check = 1;//如果循环结束还是1则证明没有松弛任何一个值
        for (i = 1; i <= m; i++)
        {
            if(dis[y[i]] > dis[x[i]] + x_y_dis[i])
            {
                dis[y[i]] = dis[x[i]] + x_y_dis[i];
                check = 0;
            }
        }
        if(check == 1)//如果不能松弛任何一个值,那么跳出循环
            break;
    }

    if(check == 0)//只有最后一次循环还是有松弛的情况下才有可能出现无限松弛的负权环路
    {
        check=1;
        //判断是否有负权环路
        for (i = 1; i <= m; i++)
        {
            if(dis[y[i]] > dis[x[i]] + x_y_dis[i])
            {
                dis[y[i]] = dis[x[i]] + x_y_dis[i];
                check = 0;
            }
            if(check == 0)
                break;
        }
        if(check == 0)
        {
            cout<<"这个图有负权回路,没有最短路径"<<endl;
        }
    }

    if(check != 0)//经过上面的判断只有如果没有负权回路,就输出
    {
        for (i = 1; i <= n; i++)
        {
            cout<<dis[i]<<" ";
        }
        cout<<endl;
    }
    else
        return 0;

}

/*

6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
Result:0 1 8 4 13 17

5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
Result:0 -3 -1 2 4
*/

 

最后想提的是,这个算法的时间复杂度是O(NM)也就是当M特别多的时候(题目故意恶心你)需要进一步的优化处理,我之后再实现。

但是这个算法的优点就是能解决负边权的问题。

和Dijkstra相比,Dijkstra是在两个点之间加一个点,利用点去循环更新一个距离之后就认为这个距离已经是最优解了,后面利用最优解去不断取出后面点的最优解。而Bellman-Ford是循环输入的边,让边的不断增加去更新距离,所以当边不断的增加,距离也就更新了,无论你距离的正负。

posted @ 2016-03-25 23:59  LinkinStar  阅读(277)  评论(0编辑  收藏  举报