最短路四大算法证明以及分析(Flord Bijkstra Bellman-ford SPFA)

此博文不具体给出其算法的代码,只对其中算法进行分并且给予证明

PS:这些算法我不用证明都是它是正确的(上世纪的数学家看着这些都不用证明,为啥,很简单的),但是我坚持重新证明一遍实际是为了加深印象,并且理解其中的道理和思想,这样在以后的运用中才能灵活运用,当然证明这些算法也


算法一:Flord 算法,也是传说中的只用五行就可以解决的多源最短路径问题

采用邻接矩阵来储存图,时间复杂度为O(n^3),能解决含正权,负权的最短路径,不能解决含有负的最短路径(负环也没有最短路径)

for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(e[i][j]>e[i][k]+e[k][j])
                 e[i][j]=e[i][k]+e[k][j];

当k=1时进行一个嵌套的for循环求出来的是任意两点之间只允许经过点1的最短路径,这个都可以理解吧。

( ̄▽ ̄)"

当k=2时,经过一个if else的判断最终求出来的是任意两个顶点之间只允许经过1 2号顶点的最短路径。

这时候可能有人说在进行 if      else 之后判断的是i->j通过2号顶点路径后路径能否缩小,那么如果i->j还能通过1号顶点再次缩小路径,那还能体现出来吗?

好的下面咱们来证明

假设咱们判断过之后i->j之间通过2号顶点可以缩小路径,此时i与j的路径为i->2->j,假设还可以通过1号顶点缩小路径(也就是上面所说的情况)

此时1号顶点肯定在i->j之间,也就是i->2或2->j之间 ,而咱们在第一次for循环之后的意义上面已经说过了,即任意两点可以经过1号顶点后更新的最短路径,那么回过来即i->2之间或者2->j之间若可以经过1号顶点在缩短路径,那么铁定在第一次循环后就已经在i->2或2->j之间添加1号顶点了,所以上面的担心是多余的(●'◡'●)

当k=2的循环结束后,得到的意义便是任意两个顶点只允许经过1 2号顶点的最短路径


当k=n时  循环结束后便是任意两点之间允许经过1~n个顶点后的最短路径,(如果有疑问,那么再按着刚刚在2号顶点的证明过程比较一下)



算法二:dijkstra算法 (求单源最短路径,只允许正权)

这个是比较常用的算法,其常用的储存图的方式有邻接矩阵,邻接表两种。采用邻接矩阵的时间复杂度为O(n^2)

在此用邻接矩阵来举例子

先说一下算法原理,初始化就不说了吧(任意两点之间之间路径为INF,自己与自己的路径为0)随后输入图。

用一个dis[]数组记录源点到其他点的直接路径。假设源点号为S,并且用数组book[]来表示s->其他点的最短路径是否确定,确定的点令其值为1(book[s]=1)。

然后从dis[]数组找s->v最小的值,假设为这个顶点为u,那么s->u的最短路径肯定就确定了。为啥 ?因为s->u若再经过其他点所成的路径一定比s->u的路径的大。

已经确定s->u是最短路径后,松弛u点到其他点的路径,令book[u]=1。(这点是关键)

所以算法分三个步骤

1.然后再从dis[]从最短路径未确定的路径找出的最小值,令其为确定的最短路径(假设这个顶点是u),令book[u]=1;(证明下面在说)

2.从u点松弛

3.重复以上1. 2点直至所有点的最短路径确定。



也许有很多人对第1步有些不解。为什么从中找出的符合条件的u的最短路径一定确定了呢?别急下面来证明


看这个图

第一次  找出s->a最小,那么令此时s->a的路径为最短路径,令book[2]=1,(这个都没疑问吧)...... 然后松弛a->b得到mid=dis[a]+map[a][b]<dis[b]   且松弛之后s->b的路径变小了

第二次,再次按步骤1找出最小的为dis[b],此时的dis[b]为s->b的最短路径。为啥?假设此时dis[b]的值不是s->b的最短路径,那么要想找s->b的更小路径至少中转至一个顶点v,且s->v的距离满足dis[v]<dis[b](不然路径会更大),然后经v中转至b,但是因为dsi[v]<dis[b]表明v已经确定为最小路径了,前面咱们说过,确定为最短路径的点一定要经过此点到其他顶点的松弛,所以你找的所有的s->b的路径一定>=dis[b],

也就是下面这个原理


u为此时经过步骤1确定的点,假设s->u的距离不是最短路径,即存在另一条路径s->v->u(你也可以假设s->v之间还有许多点,但是我们要确保v->u是直接的,即不经过任何一个中转点)使得路径长度mid<dis[u],

又因为假设的路径中v是在s->u之间,所以v的最短路径已经确定且v经过松弛,即v到任何点的都经过松弛(包括点u),所以此时我们找的mid是一定>=dis[u]的,即假设不成立。               

所以你就默认他们是最短路径这一事实吧( ̄▽ ̄)"

代码:

附上dijkstr算法


/*
dijkstr 算法
二步:
1.初始化
2.每次找出dis最小值则为确定对短路
注意:minn=INF 或者找到答案这一提前跳出
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<iostream>
#include<cstdlib>
#define N 1010
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define WC 1e-6
typedef long long LL;
using namespace std;
int book[N],dis[N],map[N][N];
void dijkstr(int n)
{
    int t=n,minn,u,to;
    memset(book,0,sizeof(book));
    for(int i=1;i<=n;i++)
        dis[i]=INF;
    dis[1]=0;
    while(t--)
    {
        minn=INF;
        for(int i=1;i<=n;i++)
        {
            if(!book[i]&&dis[i]<minn)
            {
                u=i;
                minn=dis[i];
            }
        }
        book[u]=1;
        for(int i=1;i<=n;i++)//松弛
        {
            if(!book[i])
                {
                    to=dis[u]+map[u][i];
                    if(to<dis[i])
                        dis[i]=to;
                }
        }
    }
}
int main()
{
    //cin map 就OK
}





有时间再补充另两个算法的证明


附上SPFA代码

/*
SPFA
三步
1.初始信息 将初始点加入队列
2.每次从队列的第一个点进行松弛,可以更新另一个点则将其加入队列(已经加入队列则不需要再次加入)
2.队列为空结束
*/

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<iostream>
#include<cstdlib>
#include<queue>
#define N 1010
#define INF 0x3f3f3f3f
#define WC 1e-6
typedef long long LL;
using namespace std;
int book[N],dis[N],map[N][N];//存信息
queue<int>mmp;
void SPFA(int n)//n得出范围  输入map就OK
{
    int to;
    memset(book,0,sizeof(book));
    for(int i=1;i<=n;i++)
        dis[i]=INF;
    dis[1]=0;
    while(!mmp.empty())
        mmp.pop();
    mmp.push(1);
    while(!mmp.empty())
    {
        int u=mmp.front();//出队伍
        mmp.pop();
        book[u]=0;
        for(int i=1;i<=n;i++)//松弛
        {
            to=dis[u]+map[u][i];
            if(to<dis[i])
            {
                dis[i]=to;
                if(!book[i])
                {
                    mmp.push(i);
                    book[i]=1;
                }
            }
        }
    }
}
int main()
{
    //cin map
}





附上bellman算法

/*
bellman算法   可以判断负环
1.初始化dis (struct)edge
2.遍历所有边
2.更新成功则进行下一次
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<iostream>
#include<cstdlib>
#define N 4010
#define INF 0x3f3f3f3f
#define WC 1e-6
typedef long long LL;
using namespace std;
int dis[N];
struct node{
int u,v,w;
}e[N];
void bellman(int n,int cnt)
{
    int to,t,flag;
    for(int i=2;i<=n;i++)
        dis[i]=INF;
    dis[1]=0;
    t=n-1;
    while(t--)
    {
        flag=0;
        for(int i=0;i<cnt;i++)
        {
            to=dis[e[i].u]+e[i].w;
            if(to<dis[e[i].v])
            {
                dis[e[i].v]=to;
                flag=1;
            }
        }
        if(!flag)
            break;
    }
//    if(flag)//最后一次用于判断负环
}
int main()
{
   //cin edge
}


原文地址:https://www.cnblogs.com/dchnzlh/p/9780080.html