【C++】Dijkstra算法
算法简介迪杰斯特拉(Dijkstra)算法是图论中的最短路算法,它可以实现求解特定起点到任一点的最短路径。对于顶点个数为nnn的图,如果需要求解每两个点之间的最短路径则需要跑nnn次迪杰斯特拉算法。迪杰斯特拉的时间复杂度为O(n2)O(n^2)O(n2),缺点是不适用于带负权边的图。算法思想迪杰斯特拉算法基于贪心的思想,不断地寻找中间结点www使得u→w→vu\to w\to vu→w→v的距离小
算法简介
迪杰斯特拉(Dijkstra)算法是图论中的最短路算法,它可以实现求解特定起点到任一点的最短路径。对于顶点个数为nnn的图,如果需要求解每两个点之间的最短路径则需要跑nnn次迪杰斯特拉算法。
迪杰斯特拉的时间复杂度为O(n2)O(n^2)O(n2),缺点是不适用于带负权边的图。
算法思想
迪杰斯特拉算法基于贪心的思想,不断地寻找中间结点www使得u→w→vu\to w\to vu→w→v的距离小于u→vu\to vu→v的距离,即用中间值更新结果。相似算法思想参考朴素迪杰斯特拉算法邻接矩阵法。
朴素迪杰斯特拉算法
邻接矩阵法
这是最简单的迪杰斯特拉算法,采用邻接矩阵存储,这里以邻接矩阵map[i][j]map[i][j]map[i][j]表示iii到jjj的距离,顶点到自身距离map[i][i]=0map[i][i]=0map[i][i]=0,如果iii到jjj没有边则map[i][j]=∞map[i][j]=\infinmap[i][j]=∞。
对于一个顶点个数为nnn的图,若求点xxx到任一点的最短路径长度,迪杰斯特拉维护一个长度为nnn的数组dist[]dist[]dist[],dist[y]dist[y]dist[y]表示xxx到yyy的最短路径长度。其次,还维护了一个长度为nnn的visit[]visit[]visit[]数组,visit[j]visit[j]visit[j]表示jjj是否被访问过。
- 对于distdistdist数组,首先初始化dist[]dist[]dist[]为∞\infin∞表示xxx到任一点距离无穷大,但是dist[x]=0dist[x]=0dist[x]=0表示xxx到自己距离为000,初始化visit[]visit[]visit[]数组值为000表示点未被访问,执行第222步。
- 查找未被访问过的顶点中使得dist[j]dist[j]dist[j]最小的顶点jjj,并把jjj标记为已访问,即visit[j]=1visit[j]=1visit[j]=1,执行第333步。
- 用jjj去更新路径,查看以jjj作为中间结点是否比已知路更短,更短则更新。dist[y]=min(dist[y],dist[j]+map[j][y])dist[y]=min(dist[y],dist[j]+map[j][y])dist[y]=min(dist[y],dist[j]+map[j][y]),回到第222步,直到所有顶点都被访问。
void Dijkstra(int x){//求x到任一点
memset(dist,INF,sizeof(dist));//dist[]=INF
dist[x]=0;
memset(visit,0,sizeof(visit));
for(int i=1;i<=n;i++){
int j=0;
for(int k=1;k<=n;k++){//找最小未被访问的dist[j]
if(!visit[k]&&dist[k]<=dist[j]){
j=k;
}
}
visit[j]=1;
for(int k=1;k<=n;k++){//更新dist[j]
dist[k]=min(dist[k],dist[j]+map[j][k]);
}
}
}
邻接表法
除了易于实现的邻接矩阵,迪杰斯特拉算法同样可以改写成邻接表形式。这里采取链式前向星结构,其实所谓的链式前向星,就是用数组模拟实现的链表,也就是静态链表。head[]head[]head[]数组表示顶点数组,指向与顶点连接的边的下标。to[k]to[k]to[k]数组表示第kkk条边的指向顶点编号,val[k]val[k]val[k]表示第kkk条边的权值。
void Dijkstra(int x){//求x到任一点
memset(dist,INF,sizeof(dist));//dist[]=INF
dist[x]=0;
memset(visit,0,sizeof(visit));
for(int i=1;i<=n;i++){
int j=0;
for(int k=1;k<=n;k++){//找最小未被访问的dist[j]
if(!visit[k]&&dist[k]<=dist[j]){
j=k;
}
}
visit[j]=1;
for(int k=head[j];k;k=next[k]){//更新dist[j]
dist[to[k]]=min(dist[to[k]],dist[j]+val[k]);
}
}
}
堆优化迪杰斯特拉算法
- 朴素迪杰斯特拉算法的可优化点在于查找未被访问过的满足最小dist[j]dist[j]dist[j]的顶点jjj,这个过程可以采取堆优化,也就是所谓的优先队列。
算法思想
优化查找过程,首先初始化堆只有源点,初始化dist[]=INFdist[]=INFdist[]=INF,除了dist[x]=0dist[x]=0dist[x]=0。然后每次从堆中取出最小距离的中间结点jjj并出堆,将所有能更新的dist[j]+map[j][k]<dist[k]dist[j]+map[j][k]<dist[k]dist[j]+map[j][k]<dist[k]的dist[k]dist[k]dist[k]更新为dist[j]+map[j][k]dist[j]+map[j][k]dist[j]+map[j][k]并将结点kkk入堆,重复此过程直到堆空为止。
提示
- 由于朴素算法是用数组编号来映射结点的,而堆排序会打乱原映射顺序,所以我们考虑用C++C++C++自带的pair<typeA,typeB>pair<typeA,typeB>pair<typeA,typeB>来存储结点,由于pairpairpair默认按照typeAtypeAtypeA排序,所以typeAtypeAtypeA应该是距离,typeBtypeBtypeB是结点编号。
- c++c++c++的priority_queuepriority\_queuepriority_queue默认是大顶堆,所以需要重载比较运算符。
- priority_queuepriority\_queuepriority_queue位于<queue><queue><queue>,pairpairpair位于<utility><utility><utility>。
邻接矩阵法
void Dijkstra(int x){//求x到任一点
memset(dist,INF,sizeof(dist));//初始化最远距离
typedef pair<int,int> pr;//分别存储距离和顶点编号
priority_queue<pr,vector<pr>,greater<pr> > Q;//优先队列,重载运算符
dist[x]=0;//初始化源点自身距离为0
Q.push(make_pair(0,x));
while(!Q.empty()){
int j=Q.top().second;
Q.pop();
if(visit[j]) continue;
visit[j]=1;
for(int k=1;k<=n;k++){//更新dist[j]
if(dist[j]+map[j][k]<dist[k]){
dist[k]=dist[j]+map[j][k];
if(!visit[k]) Q.push(make_pair(dist[k],k));
}
}
}
}
邻接表法
同样的,堆优化也可以采用链式前向星结构,详细数组含义参考朴素算法的邻接表法。
void Dijkstra(int x){//求x到任一点
memset(dist,INF,sizeof(dist));//初始化最远距离
typedef pair<int,int> pr;//分别存储距离和顶点编号
priority_queue<pr,vector<pr>,greater<pr> > Q;//优先队列,重载运算符
dist[x]=0;//初始化源点自身距离为0
Q.push(make_pair(0,x));
while(!Q.empty()){
int j=Q.top().second;
Q.pop();
if(visit[j]) continue;
visit[j]=1;
for(int k=head[j];k;k=last[k]){//更新dist[j]
if(dist[j]+val[k]<dist[to[k]]){
dist[to[k]]=dist[j]+val[k];
if(!visit[to[k]) Q.push(make_pair(dist[to[k]],to[k]));
}
}
}
}
总结
迪杰斯特拉算法堆优化后效率很高,优于SPFASPFASPFA算法,但是不能用于带负权边的图,时间复杂度上堆优化效率高于朴素算法,空间复杂度上邻接表法优于邻接矩阵法。
更多推荐
所有评论(0)