贪心算法—迪杰斯特拉算法(Dijkstra)

    Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,读大学时小编也学习过该算法,但理解不是特别透彻,利用这段时间,小编也重新学习了一遍,并把学习过程分享给大家。

一、单源最短路径问题

    首先我们在算这个最短路径的时候,针对的是带权有向图,其中每条边的权是非负实数。我们给定一个带权有向图G单源最短路径问题(Single-Source Shortest Paths)。

    那为什么要是权值不为负呢,我们举个最简单的反例:

贪心算法—迪杰斯特拉算法(Dijkstra)_第1张图片
G1

    在上图中,顶点V1到V2的路径有两条,V1->V2和V1->V3->V2。第一条路径长度为2,第2条路径长度为1。但实际上第1条路径才是最短的。

二、算法分析

    迪杰斯特拉(Dijkstra)算法是一个按照路径长度递增的次序产生最短路径的算法。主要特点是以起始点为中心按照长度递增往外层层扩展(广度优先搜索的思想),直到扩展到终点为止。

    1、所需数据结构

    我们给定一个有权图G=,为了更好的对分析过程进行理解,这里小编把几个需要的辅助数据结构解释一下:

    ① 顶点集V:图中所有点的集合。

    ② 点集S:已经找到最短路径的终点集合。

    ③ 点集U:未计算出最短路径的顶点的集合。

    ④ 辅助数组Distance【简称D】:D[i]表示当前所找到的从起始点v0【也称源点,自己定义】到每个终点vi的最短路径长度。

    D的初态为:若V0到Vi有弧,则D[i]为弧上的权值,否则置D[i]为∞。

    ⑤ 邻接矩阵arcs:带权有向图。arcs[i][j]表示弧上上的权值。若不存在,则置arcs[i][j]为∞。

    ⑥ 数组path:起点V0与顶点i的最短路径为V0->...->path[i]->i,即path[i]为该路径中i的前一个结点。

    2、从实例来看算法流程

    直接上算法流程可能有点难懂,直接从例子入手来看看,以下图G为例:

贪心算法—迪杰斯特拉算法(Dijkstra)_第2张图片
G

    首先初始化下列两个数组:

    D[]={∞,∞,∞,∞,∞,∞}【在这里,ABCDEF按照顺序排列(012345)。D[0]表示起点A到自己的路径距离为∞,D[1]表示起点A到顶点B的路径距离为∞,D[2]表示顶点A到顶点C的路径距离为∞,D[3],D[4],D[5]同理】。

    path[]={-1,-1,-1,-1,-1,-1}。

    选取一个起始点【任选】,这里我们选取起始点为A,并设置D[0]=0,

    此时D[]={0,∞,∞,∞,∞,∞};

    第一步:选取A,加入集合S。在图G中,有:

    S={A}【表示最短路径A->A=0】;

    U=V-S={B,C,D,E,F};

    然后以A为中间点,有:

    A->B=1,也就是D[0]+arc[0][1]

    A->C=12,也就是D[0]+arc[0][2]

    A->D=∞,不存在D[0]+arc[0][3]

    A->E=∞,不存在D[0]+arc[0][4]

    A->F=∞,不存在D[0]+arc[0][5]

    此时,D[]={0,1,12,∞,∞,∞},path[]={-1,0,0,-1,-1,-1}。

    第二步:选取B,加入S,有:

    S={A,B}【最短路径A->A=0,A->B=1】;

    U=V-S={C,D,E,F};

    然后以B为中间点,查找发现B->C=9,B->D=3,B->E=∞,B->F=∞。

    (A->B+B->C=1+9=10)<(A->C=12),也就是D[1]+arc[1][2]

    (A->B+B->D=1+3=4)<(A->D=∞),也就是D[1]+arc[1][3]

    因此:

    A->B->C=10

    A->B->D=4

    A->B->E=∞

    A->B->F=∞

    对数组D进行更新,得到D[]={0,1,10,4,∞,∞},path[]={-1,0,1,1,-1,-1}

    第三步:选入D,加入S,有:

    S={A,B,D}【最短路径A->A=0,A->B=1,A->B->D=4】;

    U=V-S={C,E,F};

    然后以D为中间点,查找发现D->C=4,D->E=13,D->F=15。

    (A->B->D+D->C=4+4=8)<(A->B->C=10),也就是D[3]+arc[3][2]

    (A->B->D+D->E=4+13=17)<(A->E=∞),也就是D[3]+arc[3][4]

    (A->B->D+D->F=4+15=19)<(A->F=∞),也就是D[3]+arc[3][5]

    因此:

    A>B->D->C=8

    A->B->D->E=17

    A->B->D->F=19

    对数组D进行更新,得到D[]={0,1,8,4,17,19},path[]={-1,0,3,1,3,3}

    第四步:选入C,加入S,有:

    S={A,B,D,C}【最短路径A->A=0,A->B=1,A->B->D=4,A->B->D->C=8】;

    U=V-S={E,F};

    然后以C为中间点,查找发现C->E=5,C->F=∞。

    (A->B->D->C+C->E=8+5=13)<(A->B->D->E=17),也就是D[2]+arc[2][4]

    (A->B->D->C+C->F=8+∞=∞)>(A->B->D->F=19),也就是D[2]+arc[2][5]>D[5],D[5]以及path[5]都不进行更新;

    因此:

    A>B->D->C->E=13

    A->B->D->F=19

    对数组D进行更新,得到D[]={0,1,8,4,13,19},path[]={-1,0,3,1,2,3}。

    第五步:选入E,加入S,有:

    S={A,B,D,C,E}【最短路径A->A=0,A->B=1,A->B->D=4,A->B->D->C=8,A>B->D->C->E=13】;

    U=V-S={F};

    然后以E为中间点,查找发现E->F=4。

    (A->B->D->C->E+E->F=13+4=17)<(A->B->D->F=19),也就是D[4]+arc[4][5]

    因此:

    A>B->D->C->E->F=17

    对数组D进行更新,得到D[]={0,1,8,4,13,17},path[]={-1,0,3,1,2,4}

    第六步:选入F,加入S,有:

    S={A,B,D,C,E,F}【最短路径A->A=0,A->B=1,A->B->D=4,A->B->D->C=8,A>B->D->C->E=13,A>B->D->C->E->F=17】;

    U={};

    此时查找结束。D[]={0,1,8,4,13,17},path[]={-1,0,3,1,2,4}。

    通过数组D,就能知道起点A到其他各个顶点的最短路径,分别为0,1,8,4,13,17。

    那怎么通过path数组输出最短路径经过的顶点呢?

    A到F的路径:path[5]=4,path[4]=2,path[2]=3,path[3]=1,path[1]=0,path[0]=-1,停止。此时A到F的路径【这里为逆序】就为5<-4<-2<-3<-1<-0,也就是F<-E<-C<-D<-<-B<-A。

    A到E的路径:path[4]=2,path[2]=3,path[3]=1,path[1]=0,path[0]=-1,停止。此时A到E的路径【这里为逆序】就为4<-2<-3<-1<-0,也就是E<-C<-D<-<-B<-A。

    A到D的路径:path[3]=1,path[1]=0,path[0]=-1,停止。此时A到D的路径【这里为逆序】就为3<-1<-0,也就是D<-<-B<-A。

    A到C的路径:path[2]=3,path[3]=1,path[1]=0,path[0]=-1,停止。此时A到C的路径【这里为逆序】就为2<-3<-1<-0,也就是C<-D<-

    A到B的路径:path[1]=0,path[0]=-1,停止。此时A到B的路径【这里为逆序】就为1<-0,也就是B<-A。

    3、算法流程总结

    把上面的流程进行归纳,就是我们课本上经常描述的文字了。

    用带权值的邻接矩阵arc来表示带权的有向图,V,S,U,D在前面已经介绍过。

    (1)初始化:顶点集S为空,从V0出发到图上其余各顶点Vi可能到达的最短路径长度初值:D[i]=arc[locate(G,V0)][i](Vi ϵ V), 也就是D[0]=0,其他为+∞。

    (2)选择节点Vj,加入集合S,U=V-S,使得:D[j]=Min{D[i]|Vi ϵ U}。其中Vj就是当前求的从v0出发的最短路径的终点

    (3)修改从V0出发到集合U上的任一顶点Vk可达的最短路径长度,若:D[j]+arc[j][k]

    (4)重复(2)和(3),直到U为空。求的V0 到其余顶点的最短路径是依路径长度递增的序列。

三、Dijkstra算法的数学证明

    从算法的数学描述可知,只要证明命题“从U中找到D[i]最小的点j,D[j]即为从起点到j的最短路径长度”正确,算法就正确。【i,j为顶点集中的顶点下标】

    设置起点为V0,下标为0。

    (1)算法找到的第一个点为Vj,下标为j。D[j]即为从起点V0到Vj的最短路径长度。采用反证法:

    因为算法找到的第一个点一定是起点V0最近的邻居;

    假设D[j]不是从起点到V0的最短路径长度,则存在点Vk,使得|V0->Vk|<|V0->Vj|;

    与已知矛盾,故假设不成立,子命题成立。

    (2)已用算法从U中依次找到了V1,V2,...Vk共k个点,且D[1],D[2]...D[k]是起点到各顶点的最短路径长度,则此时继续从U中依照算法再找一个点V(k+1),D[k+1]就是为起点V0到V(k+1)的最短路径长度。

    同样采用反证法:

    假设D[k+1]不是起点V0到V(k+1)的最短路径长度,所以设置起点到V(k+1)的最短径经过的点的集合为T,路径长度为L,则有L

    由D[i]的含义可知T∩U不为空集

    因为V0ϵT且V0 ϵS,可得到T∩S不为空集

    设到V(k+1)的最短路径中,最靠近V(k+1)且不属于S的点为Vx,Vx的后继为Vy;

    因为有向图中边均为正权边;

    所以有D[x]

    又因为Vx不属于S,因此有D[x]>D[k+1],产生矛盾,故该假设也不成立。

    综上所述,该算法是正确的。

四、代码实现

    下面是一个邻接矩阵的完整代码,图中就有这个最短路径算法的函数,截图如下所示:

贪心算法—迪杰斯特拉算法(Dijkstra)_第3张图片
C++算法

    有误请指正,谢谢!

你可能感兴趣的:(贪心算法—迪杰斯特拉算法(Dijkstra))