Dijkstra 算法,优先队列法
如前所述,Dijkstra算法是图论中非常重要的算法之一。前一篇对严蔚敏《数据结构》进行梳理和代码实现,严版的算法中利用P[v][w] ,D[w] 和 final[v] 对源点和各个之间的最短距离的路径,长度和访问标记进行记录,最终求得SSSP(single source shortest path)路径。
本文主要介绍如何利用Priority Queue(PQ)队列,求得源点至各个顶点的最短距离。对于Priority Queue的实现,这里不做赘述,有兴趣的可以参考网上的相关文章介绍,本文主要用到min Priority Queue,队列的顶点永远保持最小值的元素。
实现算法之前,我们需要抽象几个数据结构体,首先需要定义Priority Queue的堆中储存的数据对象类型,我们这里采用记录顶点序列号和到达此顶点的最短距离两个记录。
a) Dijkstra_Node 结构体
/**
@param -index indicates the visiting vertex order
@param -dist indcates the current min distance from source to here
**/
typedef struct Dijkstra_Node
{
int index;
int dist;
} Dijkstra_Node;
b) dist[] 数组,dist将记录源点到此点的最短距离,过程中需要不断利用贪心思想,最总求得某个顶点的最小dist[w]。
c) prev[]数组,记录某个顶点的前置顶点,便于后续遍历时,能找到完整的最短路径,否则就只能得到最小值或最短距离,无法获取其具体路径。
d) visited[]数组,这个数组属于在邻接图中有定义,如果访问过,标记为1
利用William提供的示意图,对PQ为基础的Dijkstra进行说明。
- 首先需要对dist数组进行初始,推定各个点距离源点S的距离为INT_MAX(机器中的最大整数),然后把pre[]赋值为-1,表明各个顶点还暂未前置顶点,最后把visited[]初始化为0,表明各节点还未开始访问。
- 然后以源点为起起始点,遍历其可达的顶点,如果可达顶点还未访问,那么就需要加入到PQ列表中去
- 在PQ中出栈最小的(index, dist)序列,重复以上步骤。
过程中可能存在存在一类情况,就是到达某个顶点有多个PQ记录,这时候可以对重复的顶点记录(dist不同)进行skip处理,因为相对某个顶点的最小距离遍历后,再取相同顶点的较大距离,不会产生更好的结果。
上述遍历完成(1,3)之后,就没有必要再遍历(1,4),因为对于顶点1,后续出栈的距离,至少大于等于前面出栈的距离,也即4>3。所以对(1,4)直接出栈不需要进行后续任何操作。
由于Priority Queue已经采用C语言实现,自然而然本算法也沿用C语言进行后续的继续操作,包括结构体和函数的定义实现。
a) 头文件说明,本头文件中已经包含了PQ的实现,读者可以自己编写PQ,编写过程中,可以预定义Heap中的对象,在实际应用中可以重新定义(ShortestPath.h)。
/**
* @file ShortestPath.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023
*
*/
#ifndef SHORTESTPATH_H
#define SHORTESTPATH_H
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#include "../00_introduction/Status.h"
#include "../02_Adjacent_List_Graph/AdjacentListGraph.c"
typedef struct Dijkstra_Node
{
int index;
int dist;
} Dijkstra_Node;
typedef Dijkstra_Node priority_queue_element;
#include "../11_PriorityQueue/PriorityQueue.c"
/**
* @brief Use priority queue to look for the single source shortest path
*
* @param G Adjacent list graph
* @param s Search start point
* @param dist dis[] array to store each node minimum distance
* @param prev prev[] array to store the previous index
*/
void Dijkstra_Lazy(ALGraph G, int s, int *dist, int *prev);
/**
* @brief Display the short path
*
* @param G Adjacent list graph
* @param s Search start point
* @param e Search end point
* @param prev prev[] array to store the previous index
*/
void Display_Shortest_Path(ALGraph G, int s, int e,int *prev);
/**
* @brief Display the minimum distance
*
* @param e end point
* @param dist dist[] array to store each node minimum distance
*/
void Display_Distance(int e, int *dist);
/**
* @brief Compare e1 and e2 with regard to its own dist component
*
* @param e1 object 1
* @param e2 object 2
* @return int -Return true if e1<e2
*/
int less_than(priority_queue_element e1, priority_queue_element e2);
#endif
b) 函数实现(ShortestPath.c)
/**
* @file ShortestPath.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023
*
*/
#ifndef SHORTESTPATH_C
#define SHORTESTPATH_C
#include "ShortestPath.h"
void Dijkstra_Lazy(ALGraph G, int s, int *dist, int *prev)
{
int i;
int index;
int minvalue;
Heap H;
priority_queue_element node;
ArcNode *p;
int v;
int w;
init_min_heap(&H,G.arcnum*2);
for(i=0;i<G.vexnum;i++)
{
visited[i]=0;
*(dist+i)=INT_MAX;
*(prev+i)=-1;
}
dist[s]=0;
node.index=s;
node.dist=0;
min_heap_insert(H,node,less_than);
while(H->size>0)
{
node=heap_extract_min(H,less_than);
v=node.index;
minvalue=node.dist;
visited[v]=1;
if(dist[v]<minvalue)
{
continue; // continue extract or dequeue procedure
}
for(p=G.vertices[v].firstarc;p;p=p->nextarc)
{
w=p->adjvex;
if(!visited[w])
{
if((dist[v]+*(p->info))<dist[w])
{
dist[w] = dist[v] + *(p->info);
prev[w]=v;
node.index=w;
node.dist = dist[v] + *(p->info);
min_heap_insert(H,node,less_than);
}
}
}
}
}
void Display_Shortest_Path(ALGraph G, int s, int e, int *prev)
{
if(s!=e)
{
Display_Shortest_Path(G,s,prev[e],prev);
}
printf("-%c-\n",G.vertices[e].data);
}
void Display_Distance(int e, int *dist)
{
printf("The minimum distance is %d \n",*(dist+e));
}
int less_than(priority_queue_element e1, priority_queue_element e2)
{
return (e1.dist<e2.dist);
}
#endif
c) 代码测试(ShortestPath_main.c)
/**
* @file ShortestPath_main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023
*
*/
#ifndef SHORTESTPATH_MAIN_C
#define SHORTESTPATH_MAIN_C
#include "ShortestPath.c"
int main(void)
{
// void Display_Shortest_Path(ALGraph G, int s, int e, int *dist, int *prev)
int dist[MAX_VERTEX_NUM];
int prev[MAX_VERTEX_NUM];
int s;
int e;
ALGraph G;
FILE *fp;
s=0;
e=5;
fp=fopen("DN.txt","r");
CreateGraph(&G,fp);
Dijkstra_Lazy(G,s,dist,prev);
Display_Shortest_Path(G,s,e,prev);
Display_Distance(e,dist);
PressEnter;
fclose(fp);
return EXIT_SUCCESS;
}
#endif
总结,为什么称之此版本为LAZY版本,因为对于后续访问的顶点,可能存在多个(index, dist),这个时候lazy版本全盘插入,而不是对原有的顶点进行更新。所以称之为 lazy. 后续将采用eager版本进行改进。
以上,
谢谢
参考:
Video Dijkstra’s shortest path algorithm by William Fiset
PQ实现,参考《算法》第二版