前文介绍的Floyd-Warshell算法是解决多源最短路径问题[mutiple-source shortest-paths problem],即多个起始点的最短路径,而Dijkstra最短路径算法则是解决单源最短路径问题[single-source shortest-paths problem],也即是给定一个点,只需计算出该点到达另一点的最短路径即可。Dijkstra算法是贪心算法思想的成功应用,其核心思想是每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径

首先考虑下图的路径信息,现在想要知道节点1到分别到2,3,4,5,6的最短距离。

Dijkstra Algorithm

我们依然使用一个距离矩阵来描述各个点之间的距离。

1 2 3 4 5 6
1 0 1 12 X X X
2 X 0 9 3 X X
3 X X 0 X 5 X
4 X X 4 0 13 15
5 X X X X 0 4
6 X X X X X 0

除此之外,Dijkstra算法还需要维护一个一维数组来表示节点1到其他节点的距离。其初始值即为矩阵第一列,也就是最开始,节点1到达其他节点的距离。

1 2 3 4 5 6
0 1 12 X X X

然后,因为是以节点1为起始,先寻找距离节点1最近的点,跟节点1相邻的点是2和3,2为最近的点。那么现在节点1到节点2的距离就从估计值变为确定值 [确定值用粗体显示],因为我们已经从节点1开始选中最短的路径,其他任何路径包括有中转节点的情况,都不可能比这条路径更短。

1 2 3 4 5 6
0 1 12 X X X

既然已经选中节点2,那么下面就开始考虑节点2,从节点2可以到达节点3和节点4。然后就需要看这两天路线的出现能否将原有的1->3和1->4变短,即比较e[1][3]和e[1][2]+e[2][3], e[1][4]和e[1][2]+e[2][4]。根据计算结果更新数组

1 2 3 4 5 6
0 1 10 4 X X

因为1->2的距离已经确定,再从3,4,5,6中选择距离1最近的距离,也就是节点4[距离为4],则4成为一个确定值。

1 2 3 4 5 6
0 1 10 4 X X

以此类推,因为选中了4,则观察从节点4可以到达的节点有3,5,6,然后看新的路径能否缩短原路长度,也就是比较e[1][3]和e[1][4]+e[4][3],e[1][5]和e[1][4]+e[4][5],e[1][6]和e[1][4]+e[4][6],然后根据结果更新数组,

1 2 3 4 5 6
0 1 8 4 17 19

然后再从3,5,6中选择离1最近的点也就是3,则1->3的距离8就变为确定值。

1 2 3 4 5 6
0 1 8 4 17 19

然后考虑3的相邻节点并重新计算路径更新数组,3的相邻节点只有5,所以直比较e[1][5]和e[1][3] + e[3][5]

1 2 3 4 5 6
0 1 8 4 13 19

然后再次从5,6中选择离1最近的点,就是5,距离13也就成为确定值

1 2 3 4 5 6
0 1 8 4 13 19

然后5可以到达的节点只有6,所以比较e[1][6]和e[1][5]+e[5][6],

1 2 3 4 5 6
0 1 8 4 13 17

现在只剩下节点6,并且节点6也无法到达任意节点,所以17就成为确定值。

1 2 3 4 5 6
0 1 8 4 13 17

具体步骤:

  • 将所有的顶点分为两部分:已知最短路程的顶点集合P和未知最短路径的顶点集合Q。最开始,已知最短路径的顶点集合P中只有源点一个顶点。我们这里用一个book[i]数组来记录哪些点在集合P中。例如对于某个顶点i,如果boo[i]为1则表示这个顶点在集合P中,如果book[i]为0则表示这个顶点在集合Q中。

  • 设置源点s到自己的最短路径为0即dis=0。若存在源点有能直接到达的顶点i,则把dis[i]设为e[s][i]。同时把所有其它[源点不能直接到达的]顶点的最短路径为设为∞。

  • 在集合Q的所有顶点中选择一个离源点s最近的顶点u[即dis[u]最小]加入到集合P。并考察所有以点u为起点的边,对每一条边进行松弛操作。例如存在一条从u到v的边,那么可以通过将边u->v添加到尾部来拓展一条从s到v的路径,这条路径的长度是dis[u]+e[u][v]。如果这个值比目前已知的dis[v]的值要小,我们可以用新值来替代当前dis[v]中的值。

  • 重复第3步,如果集合Q为空,算法结束。最终dis数组中的值就是源点到所有顶点的最短路径。

对应的完整代码:

#include <stdio.h>
int main()
{
    int e[10][10],dis[10],book[10],i,j,n,m,t1,t2,t3,u,v,min;
    int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值
    //读入n和m,n表示顶点个数,m表示边的条数
    scanf("%d %d",&n,&m);
                                                                   
    //初始化
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(i==j) e[i][j]=0;
              else e[i][j]=inf;
                                                                             
    //读入边
    for(i=1;i<=m;i++)
    {
        scanf("%d %d %d",&t1,&t2,&t3);
        e[t1][t2]=t3;
    }
    //初始化dis数组,这里是1号顶点到其余各个顶点的初始路程
    for(i=1;i<=n;i++)
        dis[i]=e[1][i];
    //book数组初始化
    for(i=1;i<=n;i++)
        book[i]=0;
    book[1]=1;
                                                                   
    //Dijkstra算法核心语句
    for(i=1;i<=n-1;i++)
    {
        //找到离1号顶点最近的顶点
        min=inf;
        for(j=1;j<=n;j++)
        {
            if(book[j]==0 && dis[j]<min)
            {
                min=dis[j];
                u=j;
            }
        }
        book[u]=1;
        for(v=1;v<=n;v++)
        {
            if(e[u][v]<inf)
            {
                if(dis[v]>dis[u]+e[u][v])
                    dis[v]=dis[u]+e[u][v];
            }
        }
    }
                                                                   
    //输出最终的结果
    for(i=1;i<=n;i++)
        printf("%d ",dis[i]);
                                                                       
    getchar();
    getchar();
    return 0;
}

本文部分内容源自互联网,坐在马桶上看算法。