Dijkstra最短路径算法详解

简介

Dijkstra最短路径算法是非常经典的图搜索算法,而且具有一定难度,需要花时间好好理解。算法导论第24章对此有详细的分析,值得研究一番。而我自己也结合一个具体实例,实现了代码,确实只有在编码的过程中才能理解算法的细节,提高自己的算法能力。

实例分析

1.分步解析

Dijkstra最短路径算法也叫单源最短路径算法,意思就是只需要输入一个起点,求出该起点到图中其余点的最短路径即可。(而floyd则是求任意两点间的最短路径)
假如有如下有向图(无向图也可以,不能存在负值的边):
《Dijkstra最短路径算法详解》

依旧使用二维数组存储这幅图的点和边:
《Dijkstra最短路径算法详解》

还需要一个distance数组,用于存储起点到其余终点的最短距离:
《Dijkstra最短路径算法详解》

假设我们的起点即为1号顶点,由上图可知,此时它只有到点2,点3两条路。要找点1到剩余2,3,4,5,6号顶点的最短距离,那么当然只能先到点2,或点3,再到4,5,6号点。而应该通过走边(1–>2) 还是走边 (1–>3)再走到其他的点呢?相信正常人都会走最短的那条,也就是边(1–>2)。

也就是说,此时,我们要距离起点1,距离最近的点作为中转点,也就是2号点,将它加入到最短路径记录数组中。
既然选择了走2号点,那么从2号点出发,有两条路可走,先讨论通过(2–>3)这条边能否让1号顶点到3号顶点的路程变短。也就是说现在来比较distance[3]和distance[2]+e[2][3]的大小。其中distance[3]表示1号顶点到3号顶点的路程。distance[2]+e[2][3]中distance[2]表示1号顶点到2号顶点的路程,e[2][3]表示(2–>3)这条边。所以distance[2]+e[2][3]就表示从1号顶点先到2号顶点,再通过2->3这条边,到达3号顶点的路程。

我们发现原来的distance[3]=12,而distance[2]+e[2][3]=1+9=10,distance[3]>distance[2]+e[2][3],因此dis[3]要更新为10。这个过程在算法导论中的专业术语叫做边的“松弛”。1号顶点到3号顶点的路程即distance[3],通过2->3这条边松弛成功。这便是Dijkstra算法的主要思想:通过“边”来松弛1号顶点到其余各个顶点的路程。

同理,通过边(2–>4)(即e[2][4]),可以将distance[4]的值从∞松弛为4(distance[4]初始为∞,distance[2]+e[2][4]=1+3=4,distacne[4]>distance[2]+e[2][4],因此dis[4]要更新为4)。

此时,对2号顶点所有的出边进行了松弛。松弛完毕之后distance数组为:
《Dijkstra最短路径算法详解》

因为2号顶点加入了最短路径中转记录数组中,所以继续找剩下的3,4,5,6号顶点,找出它们之中距离1号起点最近的点。显然,此时点4距离起点最近,所以把点4加入最短路径中转记录数组,然后对点4的所有出边(4->3,4->5和4->6)进行松弛操作。
松弛完毕后最短路径数组变化为:
《Dijkstra最短路径算法详解》

继续在剩下的3,5,6号点中寻找离起点距离最近的顶点作为中转点,这次找到3号。对3号顶点的所有出边(3->5)进行松弛后:
《Dijkstra最短路径算法详解》

继续在剩下的5和6号顶点中,选出离1号顶点最近的顶点,这次选择5号顶点,对5号顶点的所有出边(5->4)进行松弛:
《Dijkstra最短路径算法详解》

最后只剩下6号点了,由于6号点没有出边,所以就不用松弛了。

2.代码实现:

//创建图
void createGraph(int graph[][POINTS]) {
    int mid_tmp,
        points_num,
        edges_num;
    //获取点数 & 边数
    scanf("%d %d",&points_num,&edges_num);

    //全部初始化为正无穷
    for (int i = 1;i <= points_num;++i)
        for (int j = 1;j <= points_num;++j) {
                if (i == j)
                    graph[i][j] = 0;
                else
                    graph[i][j] = INF;
        }

    //输入边
    int m,n,length;
    for (int i = 1;i <= edges_num;++i) {
        scanf("%d %d %d",&m,&n,&length);
        graph[m][n] = length;
    }
}
void showGraph(int graph[][POINTS],int n) {

    for(int i=1;i<=n;i++){    
        for(int j=1;j<=n;j++) {   
            printf("%10d",graph[i][j]);   
        }   
        printf("\n");   
    }
}   
/单源最短路径Dijkstra算法
void dijkstra(int graph[][POINTS],int points,int start_point) {
    int distance[POINTS],  //从起点到其他顶点的最短距离数组
        record[POINTS];   //新加入中转顶点记录数组

    //Stpe 1:初始化工作
    memset(distance,INF,sizeof(distance));
    for (int i = 1;i<points; ++i) 
        distance[i] = graph[start_point][i];     
        //up^: 一开始只有与起点联通的点有距离,其余均不可到达

    for (int i = 1;i<points; ++i) 
        record[i] = 0;
    record[start_point] = 1; //开始时只有起点加入标记集合中

    //***最短路径主循环***//
    int min,min_mid_ponit;
    for (int i = 1; i<= points - 1; ++i) { //找剩余的n-1个点

        //Step 2:寻找离1号顶点距离最近的中转点,不断加入到标记集合中
        min = INF;
        for (int j = 1; j <= points; ++j) {
            if (record[j] == 0 && distance[j] < min) {
                min = distance[j];
                min_mid_ponit = j;
            }
        }
        record[min_mid_ponit] = 1;  
        //up^:找到此时距离起点距离最近的点,将该点加入已知集合中

        //Step 3:通过新加入的最近中转点,更新起点到其余各点的最短路径
        int end_ponit;
        for( end_ponit = 1; end_ponit <= points; ++end_ponit ) {
            //最近中转点到终点有路可达
            if ( graph[min_mid_ponit][end_ponit] < INF ) { 
                if ( distance[end_ponit] > distance[min_mid_ponit] + graph[min_mid_ponit][end_ponit] ) 
                    distance[end_ponit] = distance[min_mid_ponit]+graph[min_mid_ponit][end_ponit]; 
                    //up^:更新最短路径 
            }
        }
    }

    //输出最终的结果
    cout<<"从起点"<<start_point<<"到其余各点的最短距离为:"<<endl;
    for( int i=1;i <= points; ++i )
        printf("%d ",distance[i]);
}

主函数

int main(void) {
    int graph[POINTS][POINTS];

    createGraph(graph);
    showGraph(graph,6);
    //floyd(graph,4);
    dijkstra(graph,6,1);

    system("pause");
    return 0;
}

运行结果:

《Dijkstra最短路径算法详解》

Ps:Dijkstra算法还可以用priority_queue或者斐波那契堆来进行优化。

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/LHJ884/article/details/47307111
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞