杭电 2544 最短路(bellman详解)

首先介绍一下bellman算法:

Bellman-ford算法是求含负权图的单源最短路径算法,效率很低,但代码很容易写。即进行持续地松弛(原文是这么写的,为什么要叫松弛,争议很大),每次松弛把每条边都更新一下,若n-1次松弛后还能更新,则说明图中有负环,无法得出结果,否则就成功完成。Bellman-ford算法有一个小优化:每次松弛先设一个标识flag,初值为FALSE,若有边更新则赋值为TRUE,最终如果还是FALSE则直接成功退出。Bellman-ford算法浪费了许多时间做没有必要的松弛,而SPFA算法用队列进行了优化,效果十分显著,高效难以想象。SPFA还有SLF,LLL,滚动数组等优化。

bellman的核心代码如下示例:

n,m分别代表点的个数和边的条数.

     for(int k=1;k<=n-1;k++)//遍历点的次数
        {
            for(int i=1;i<=m;i++)//遍历边的次数
            {
                if(dis[v[i]]>dis[u[i]]+w[i])//如果从u到v的距离能够通过w这条边压缩路径 就要进行松弛操作
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                }
            }
        }
两个for循环注定比弗洛伊德的复杂度低 效率会更高 .这时候我就想问了 为什么一定是经过k-1轮次的操作呢~?

为啥不是k-2或者更少呢?

这里就要考虑最坏的情况了~.如果需要松弛操作的地方比较多 那么松弛的轮数也会随之增加 .

但是并不是所有情况都需要k-1轮操作 那么如何优化算法呢?

这个时候算法大牛说话了:如果在当前一轮的操作中 没有经过松弛操作  那么这个时候就已经不用继续松弛了~

所以就有了如下代码~;

        for(int k=1;k<=n-1;k++)
        {
            check=0;//用check检查是否进行下一轮次的操作
            for(int i=1;i<=m;i++)
            {
                if(dis[v[i]]>dis[u[i]]+w[i])
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                    check=1;
                }
            }
            if(check==0)break;
        }
这里要注意 
                if(dis[v[i]]>dis[u[i]]+w[i])//如果从u到v的距离能够通过w这条边压缩路径 就要进行松弛操作
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                }
这只是在改变单源路径的权值.

只是在改变从u到v的权值

所以如果我们遇到的题目是无向图 如2544(杭电)就需要如下代码来完成操作:

        for(int k=1;k<=n-1;k++)
        {
            check=0;
            for(int i=1;i<=m;i++)
            {
                if(dis[v[i]]>dis[u[i]]+w[i])
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                    check=1;
                }
                if(dis[u[i]]>dis[v[i]]+w[i])
                {
                    dis[u[i]]=dis[v[i]]+w[i];
                    check=1;
                }
            }
            if(check==0)break;
        }


这个时候这个题的主体算法就呈现出来了 这里贴上完整代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
int dis[121212];
int u[121212];
int v[121212];
int w[121212];
int main()
{
    int n,m;
    int check;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0||m==0)break;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
        }
        for(int i=1;i<=n;i++)
        {
            dis[i]=0x1f1f1f1f;
        }
        dis[1]=0;
        for(int k=1;k<=n-1;k++)
        {
            check=0;
            for(int i=1;i<=m;i++)
            {
                if(dis[v[i]]>dis[u[i]]+w[i])
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                    check=1;
                }
                if(dis[u[i]]>dis[v[i]]+w[i])
                {
                    dis[u[i]]=dis[v[i]]+w[i];
                    check=1;
                }
            }
            if(check==0)break;
        }
        printf("%d\n",dis[n]);
    }
}

bellman算法有一个特性是弗洛伊德所不能企及的一个点 :解决负权路问题;

这里贴上解决负权路问题的代码:(以2544为例)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
int dis[121212];
int u[121212];
int v[121212];
int w[121212];
int main()
{
    int n,m;
    int check;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0||m==0)break;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
        }
        for(int i=1;i<=n;i++)
        {
            dis[i]=0x1f1f1f1f;
        }
        dis[1]=0;
        for(int k=1;k<=n-1;k++)
        {
            check=0;
            for(int i=1;i<=m;i++)
            {
                if(dis[v[i]]>dis[u[i]]+w[i])
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                    check=1;
                }
                if(dis[u[i]]>dis[v[i]]+w[i])
                {
                    dis[u[i]]=dis[v[i]]+w[i];
                    check=1;
                }
            }
            if(check==0)break;
        }
        int flag=0;//标记是否有负权回路...
        for(int i=0;i<=m;i++)
        {
                if(dis[v[i]]>dis[u[i]]+w[i])
                {
                    dis[v[i]]=dis[u[i]]+w[i];
                    flag=1;
                }
                if(dis[u[i]]>dis[v[i]]+w[i])
                {
                    dis[u[i]]=dis[v[i]]+w[i];
                    flag=1;
                }
        }
        if(flag==1){printf("0\n");continue;}
        printf("%d\n",dis[n]);
    }
}


你可能感兴趣的:(算法,杭电,bellman)