Dijkstra算法是计算有权值图的单源最短路径问题的经典算法
算法核心思想:定义一个顶点集合S{V0,V1,……Vn},初始条件下集合S中只包含起始源点。
对于所有顶点V,定义dist[V]来表示V到源点的最短距离。
每次循环在所有未被收录进集合S的顶点中,挑选出dist值最小的顶点Vmin,将其收录进集合S。
当一个顶点V被收录进集合S中时,检测V的所有邻接点的dist值是否产生变化。
注意:Dijkstra算法不能处理负权边。
变量定义和初始化:
double dist[Graph->nV];
int path[Graph->nV];
int dijkstra[Graph->nV];
dist数组用于记录任一顶点到源点的最短距离,path数组用来记录通过最短距离找到该顶点的路径(path[V]即V的前一个必经顶点),dijkstra数组用来标记顶点是否被收录进集合S;
for(int i=0;i<Graph->nV;i++){
dist[i]=__DBL_MAX__,path[i]=-1,dijkstra[i]=0;
}
dijkstra[start]=1;
dist[start]=0;
将所有顶点的dist值初始为正无穷,所有顶点的路径(指示前一顶点)初始为-1,集合S初始为空。
对于源点V,首先将其收录到集合S中,并定义源点的dist值为0;
GNode *ptr=Graph->G[start];
while(ptr){
dist[ptr->vertax]=ptr->weight;
path[ptr->vertax]=start;
ptr=ptr->next;
}
对于源点的所有邻接点,定义其dist值为源点到该点边的权重,并将path中的路径记录为源点。
循环算法:
while(1){
①寻找未被收录的顶点中dist值最小的顶点,将其收录到集合S。
如果没找到,退出循环结束算法
......
②for(对于①中找到的顶点,访问其所有邻接点,检查邻接点的dist值是否变化){
if(如果该点的dist值变小了,更新dist值,并将①中找到的顶点作为该点的前一最短路径点){
......
}
}
③打印路径
......
①每次循环先寻找dist值最小且未被收录的点V,将其收录进S。当所有点都被收录完毕后,退出循环结束算法。
int temp=FindMin(Graph,dist,dijkstra);
if(temp==-1) break;
dijkstra[temp]=1;
/*FindMin函数用来寻找未被收录的dist值最小的点*/
int FindMinDist(LGraph *Graph,double dist[],int dijkstra[]){
double min=__DBL_MAX__,mintag=-1;
for(int i=0;i<Graph->nV;i++){
if(dist[i]<min&&dijkstra[i]==0){
min=dist[i];
mintag=i;
}
}
return mintag;
}
②访问点V的所有邻接点,对于temp的所有邻接点ptr->vertex,如果其dist值变小了,更新dist值为点V的dist值与V到该点边的权值之和,并记V为该点的上一个路径点
ptr=Graph->G[temp];//访问最小dist值的所有邻接点
while(ptr){
if(dijkstra[ptr->vertax]==0)//对于每个temp的未被收录的邻接点ptr->vertex
if(dist[temp]+ptr->weight<dist[ptr->vertax]){//如果ptr->vertex收录后其dist变化,更新dist值
dist[ptr->vertax]=dist[temp]+ptr->weight;
path[ptr->vertax]=temp;//记录路径
}
ptr=ptr->next;
}
③倒序打印任意一点end到源点的路径,如果需要正序输出可以在记录路径时压栈,打印路径时出栈。
do{
printf("%d<=",i);
i=path[i];
}while(i!=-1);
完整代码:
void DijkstraShortestPath(LGraph *Graph,int start,int end){
int path[Graph->nV],dijkstra[Graph->nV];
double dist[Graph->nV];
for(int i=0;i<Graph->nV;i++)
dist[i]=__DBL_MAX__,path[i]=-1,dijkstra[i]=0;
dist[start]=0;
dijkstra[start]=1;
GNode *ptr=Graph->G[start];
while(ptr){
dist[ptr->vertax]=ptr->weight;
path[ptr->vertax]=start;
ptr=ptr->next;
}
while(1){
int temp=FindMinDist(Graph,dist,dijkstra);
if(temp==-1) break;
dijkstra[temp]=1;
ptr=Graph->G[temp];
while(ptr){
if(dijkstra[ptr->vertax]==0)
if(dist[temp]+ptr->weight<dist[ptr->vertax]){
dist[ptr->vertax]=dist[temp]+ptr->weight;
path[ptr->vertax]=temp;
}
ptr=ptr->next;
}
}
int i=end;
do{
printf("%d<=",i);
i=path[i];
}while(i!=-1);
}
int FindMinDist(LGraph *Graph,double dist[],int dijkstra[]){
double min=__DBL_MAX__,mintag=-1;
for(int i=0;i<Graph->nV;i++){
if(dist[i]<min&&dijkstra[i]==0){
min=dist[i];
mintag=i;
}
}
return mintag;
}
Dijkstra的时间复杂度主要取决于如何寻找未被收录集合S中的最小dist值的点,如果采用上述直接扫描所有邻接点的方法,其时间复杂度为O(V²)。当采用最小堆时,将所有未被收录集合S的点压入最小堆,每次寻找时出堆。其时间复杂度为O(VlogV)。
本文介绍了Dijkstra算法在计算有权值图的单源最短路径问题中的应用,详细讲解了算法的核心思想和实现步骤。通过C语言实现邻接表,讨论了处理负权边的问题,并探讨了算法的时间复杂度,包括直接扫描和使用最小堆两种情况。

2万+

被折叠的 条评论
为什么被折叠?



