最短路径算法(迪杰斯特拉算法,弗洛伊德算法)

最短路径:

非网图:指两个顶点之间经过的边的数量最少的路径

网图:指两个顶点之间经过的边上权值之和最少的路径

两种算法:

  1. 迪杰斯特拉算法:求某个源点到其余各顶点的最短路径问题
  2. 弗洛伊德算法:求图中每一对顶点之间的最短路径

1.迪杰斯特拉算法(Dijkstra)

迪杰斯特拉是一个按路径长度递增的次序产生最短路径的算法

大致道理:

引进辅助分量D,每个分量D[i]表示从初始点V_{0}到终点V_{i}的最短路径的长度。

辅助分量的初始状态为:若从V_{0}V_{i}有弧,则D[i]为弧上的权值,若没有弧,则置D[i]为无穷大

D[j]=Min{D[i] |V_{i}\in V},则最短路径就是从初始点V_{0}到终点V_{j}的这条弧,为(V_{0},V_{j})

那么长度次短的最短路径是哪条呢?

假设次短路径的终点为V_{k},那么这条路径有2种情况:

可能为(V_{0}V_{k}):即初始点V_{0}和终点V_{k}存在直接相连的弧,长度为D[k]

可能为(V_{0}, V_{j}V_{k}):即通过V_{j}V_{0}V_{k}间接相连,长度为D[j]与从V_{j}V_{k}弧上的权值之和


假设用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<V_{i}V_{k}>的权值,若不存在弧<V_{i}V_{k}>,则置值为无穷

S为已经找到从V_{0}出发最短路径的终点的集合,初始状态为空集.S={}

算法步骤:

  1. D[0]=0,D[i]=arcs[0][i];
  2. D[j]=Min{ D[i] | V_{i}\in V-S}
  3. S= S\bigcup \left \{j \right \}
  4. 修改从 V_{0}出发到集合V-S上任一顶点V_{k}可达的最短路径,如果D[j]+arcs[j][k]<D[k],修改D[k]=D[j]+arcs[j][k]
  5. 重复2~3共n-1次,求得V_{0}到剩余n-1个顶点的最短路径

代码展示:

void ShortestPath_DIJ(MGraph G,int v0, Patharc *P,ShortPathTable *D)
{    
    int final[MAXVEX];
    D[0]=0;final[0]=TRUE;//V0到自己的路径权值为0,令final[0]=TRUE,初始化将V0加入S
    for(int i=1;i<G.vexnum;i++)
    {
        final[i]=FALSE;//初始化,FALSE代表尚未寻找到最短路径
        D[i]=G.arcs[0][i];//存在弧的话即为弧上的权值,无弧则置值为无穷大
        for(int j=0;j<G.vexnum;j++)  P[i][j]=FALSE;//初始化为空路径
        if(D[i]<INFINITY){//如果存在弧,即权值D[i]小于无穷,则说明必定存在从V0到Vi的最短路径
            P[i][0]=TRUE;//V0必定在该路径中
            P[i][i]=TURE;//Vi也在该路径中
        }//如果为暂时为无穷大不代表不存在最短路径
    }//开始主循环,找到V0到剩下n-1个顶点的最短路径,分别加入S中,并令final[i]=TRUE
    for(int i=1;i<G.vexnum;i++)
    {
        int min=INFINITY;int k=0;//设最短路径为INFINITY,k为该顶点位置
        for(int j=0;j<G.vexnum;j++)
            if(!final[j]&&D[j]<min){//顶点j在V-S中,且路径权值和小于Min
                k=j;//用k标记顶点位置
                min=D[j];
            }
        final[k]=TRUE;//将顶点k加入到S,表示已经找到V0到顶点Vk的最短路径
        for(int j=0;j<G.vexnum;j++)//更新权值代价分量,看通过经顶点k的路径能否减少路径代价
            if(!final[j]&&(min+G.arcs[k][j]<D[j])){ //顶点j必须还未找到路径
                D[j]=min+G.arcs[k][j];//如果可以经过顶点k减少路径,那么更新代价
                P[j]=P[k];P[j][j]=TRUE;//将V0到Vk的路径纳入V0到Vj的路径
            }//并且P[j][j]=TRUE,原先可能值为无穷无法判断有路径,现在可以确定有V0到Vj的路径
        }
    }
}

算法距离:


2.弗洛伊德算法(Floyd):

目的:弗洛伊德算法是用于求网中任意一个顶点到任意另外一个顶点的最短路径以及最短路径代价。

为实现从任意顶点到其余所有顶点的最短路径,此时简单的算法可以是对每个顶点作为源点运行一次迪杰斯特拉算法O(n^2),等于在原来算法的基础上再来一层循环,整个算法的时间复杂度为O(n^3)

前言:弗洛伊德的《《梦的解析》》是一本心理学著作,不清楚是否是同一个人。本人学识浅薄,弗洛伊德算法虽然稍微晦涩难懂,但是它是我目前的见过的最美妙的算法,充分体现的人类思维的创造性和算法的美妙性。

思路:寻找最短路径的算法一般而言都是一步步探索,在前面的路径探索基础上得出最短的路径求解。别说计算机,就算连人这种智能生物也需要逐步的摸索才能找到一条最短的路。比如说不给你地图,让你自己去摸索从杭州到北京花钱最少的交通方式,或者耗时最少的交通方式,你也需要走一步看一步的考虑。

我们先来看一个简单的案例---最简单的3个顶点的连通网图,由浅到深看一看弗洛伊德算法。

我们定义的2个二维数组,维度为顶点数:


D[3][3]:顶点到顶点的最短路径权值和的矩阵。对于无向网来说是一个对称矩阵,对有向网则不一定是对称矩阵。

比如说:D[0][2]代表顶点V0到顶点V2的最短路径上权值之和

对无向网来说,从顶点Vi到顶点Vj的最短路径也是从顶点Vj到顶点Vi的最短路径,故矩阵对称

对有向网来说,从顶点Vi到顶点Vj存在最短路径,不代表从顶点Vj到Vi存在最短路径

在未分析任何顶点前,将D命名为D^{-1},其实它就是图的邻接矩阵,如上图


P[3][3]:代表对应顶点之间最小路径的前驱顶点矩阵(这样说就太抽象了,是我的话也看不懂)

举个例子:

假设P[1][2]=0,说明V0在从顶点V1到顶点V2的最短路径上,这条路径是从V1到V0,然后去查找从V0到V2的最短路径。也就是从V1出发,下一个落脚点是V0,至于下一个落脚点,我们的去看看从V0到V2的最短路径前驱顶点即P[0][2]即为下个落脚点。

在未进行分析之前,我们将P命名为P^{-1},此时P^{-1}\left [ i \right ]\left [ j \right ]=j,如上图

如何理解呢?初始化时,无论两个顶点是否存在最短路径,没有相连的话假设路径权值之和为无穷大就好了。顶点Vi到Vj的最短路径是直接从Vi到Vj端到端,没有中间商赚差价,从Vi出发的下一个落脚点就是终点Vj。


我们从V0开始,分析所有顶点经过V0后到达另一顶点的最短路径。

因为只有3个顶点,所以查看V1——>V0——>V2,得D^{-1}\left [ 1 \right ]\left [ 0 \right ]+D^{-1}\left [ 0\right ]\left [ 2 \right ]=3<D^{-1}\left [ 1 \right ]\left [ 2 \right ]=5

说明V1——>V0——>V2比直接V1——>V2距离还近:

D^{-1}\left [ 1 \right ]\left [ 2 \right ]=3,因为对称,所以D^{-1}\left [ 2 \right ]\left [ 1 \right ]=3,于是得到新的D^{0}矩阵,如上图

修改P^{-1}\left [ 1\right ]\left [ 2\right ]=0,因为对称,所以P^{-1}\left [ 2\right ]\left [ 1\right ]=0,由V1到V2的中间顶点为V0,于是得到新的P^{0}矩阵,如上图

即 :D^{0}[v][w]=min\left \{ D^{-1}[v][w] , D^{-1}[v][0]+D^{-1}[0][w]\right \}

于是,接下来在D^{0}P^{0}的基础上继续处理所有顶点经过V1后到达另一顶点的最短路径,得到D^{1}P^{1}

接下来在D^{1}P^{1}的基础上继续处理所有顶点经过V2后到达另一顶点的最短路径,得到D^{2}P^{2},得到结果。


接下来我们用复杂的网图来讲解弗洛伊德算法,如下图所示:

 来人!!上代码!!

typedef int Pathmatrix[MAXVEX][MAXVEX]
typedef int ShortPathTable[MAXVEX][MAXVEX]
void ShortestPath_Floyd(MGraph G,Pathmatrix *P,ShortPathTable *D)
{   //初始化矩阵D和P
    for(int v=0;v<G.numVertexes;v++)
    {
        for(int w=0;w<G.numVertexes;w++)
        {
            (*D)[v][w]=G.matrix[v][w];//初始时就是邻接矩阵
            (*P)[v][w]=w;//无中间结点,有v到w的下一落脚顶点就是w
        }
    }
    for(int k=0;k<G.numVertexes;++k)//外层循环,寻找所有顶点经过顶点k到其他顶点的最短路径,更新D和P
    {   
        for(int v=0;v<G.numVertexes;++v)
        {
            for(int w=0;w<G.numVertexes;++w)
            {
                if( (*D)[v][w]>(*D)[v][k]+(*D)[k][w] )
                {
                    (*D)[v][w]=(*D)[v][k]+(*D)[k][w];//如果经过顶点k距离更小,则更新权值
                    (*P)[v][w]=(*P)[v][k];//将由v到k的最短路径纳入v到w的最短路径
                }
            }
        }
    }
    return;
}//这代码是真的简洁优美

打印最短路径的代码为:

for(int v=0;v<G.numVertexes;v++)
{    
    for(int w=v+1;w<G.numVertexes;w++)
    {
        printf("V%d——>V%d weight:%d",v,w,D[v][w]);//打印源点和终点以及路径权值和
        int k=P[v][w];//获取第一个路径顶点下标
        printf(" Path:%d",v);//打印源点
        while(k!=w){
            printf("-->%d",k);//打印路径中间顶点
            k=P[k][w];//寻找到下一个路径终点顶点
        }
        printf("-->%d\n",w);//打印终点
    }
    printf("\n");
}

多么漂亮的算法啊!就是一个简单优美的三重循环!时间复杂度也是O(n^3),当需要求所有顶点到其他所有顶点的最短路径时,弗洛伊德算法是一个不错的算法!

参考:大话数据结构

posted on 2019-10-29 13:21  偷影子的人儿  阅读(23)  评论(0编辑  收藏  举报