Dijkstra最短路径算法详解

来源:互联网 发布:mac风扇一直转 编辑:程序博客网 时间:2024/05/19 16:20

简介

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

实例分析

1.分步解析

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

依旧使用二维数组存储这幅图的点和边:
这里写图片描述

还需要一个distance数组,用于存储起点到其余终点的最短距离:
这里写图片描述

假设我们的起点即为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数组为:
这里写图片描述

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

继续在剩下的3,5,6号点中寻找离起点距离最近的顶点作为中转点,这次找到3号。对3号顶点的所有出边(3->5)进行松弛后:
这里写图片描述

继续在剩下的5和6号顶点中,选出离1号顶点最近的顶点,这次选择5号顶点,对5号顶点的所有出边(5->4)进行松弛:
这里写图片描述

最后只剩下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;}

运行结果:

这里写图片描述

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

0 0
原创粉丝点击