迪杰斯特拉算法的基本概念
(以下摘自百度百科)
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
定义
Dijkstra算法一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。
算法思想
按路径长度递增次序产生算法:
把顶点集合V分成两组
(1)S:已求出的顶点的集合(初始时只含有源点V0)
(2)V-S=T:尚未确定的顶点集合
将T中顶点按递增的次序加入到S中,保证:
(1)从源点V0到S中其他各顶点的长度都不大于从V0到T中任何顶点的最短路径长度
(2)每个顶点对应一个距离值
S中顶点:从V0到此顶点的长度
T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度
依据:可以证明V0到T中顶点Vk的,或是从V0到Vk的直接路径的权值;或是从V0经S中顶点到Vk的路径权值之和 。
(反证法可证)
求最短路径步骤
算法步骤如下:
G = V , E G={V,E} G=V,E
- 初始时令 S = V 0 , T = V − S = 其余顶点 S={V_0},T=V-S={ 其余顶点 } S=V0,T=V−S=其余顶点,T中顶点对应的距离值
若存在, d ( V 0 , V i ) d(V_0,V_i) d(V0,Vi)为弧上的权值
若不存在, d ( V 0 , V i ) d(V_0,V_i) d(V0,Vi)为 ∞ ∞ ∞- 从T中选取一个与S中顶点有关联边且权值最小的顶点W,加入到S中
- 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值
重复上述步骤2、3,直到S [1] 中包含所有顶点,即W=Vi为止
你看看,这说的是人话吗
咳咳,不过流程图画出来之后一切就清晰了:
请各位忽略左下角那个水印。
算法执行过程
就以图G1为例,带你体验一下迪杰斯特拉算法的执行过程:
首先创建一个数组
d
i
c
t
dict
dict用来存储权值:
d
i
c
t
[
i
]
dict[i]
dict[i]表示从终点到节点i的最短路长度,把
d
i
c
t
dict
dict数组里除起点以外的元素初始化为INF,起点初始化为0(这里以1做起点,5做终点),初始化队列q把节点1加入队列
dict | Value |
---|---|
1 | 0 |
2 | INF |
3 | INF |
4 | INF |
5 | INF |
然后把节点1加入队列遍历与节点1相连的所有节点V,将它们加入优先队列,把起点踢出队列并更新节点V们的权值
于是,优先队列变成了这样:
q | w |
---|---|
3 | 1 |
4 | 3 |
5 | 5 |
接下来取出队首把队首当做节点1执行上一步,直到队列为空就行了
例题讲解
以洛谷P3371为例:传送门
首先定义一些东西:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm 500005
#define INF 1234567890
struct Edge
{
int v,w,next;
}e[maxm];
int head[maxn],cnt,n,m,s,vis[maxn],dis[maxn];
struct node
{
int w,now;
inline bool operator <(const node &x)const
//重载运算符把最小的元素放在队首
{
return w>x.w;//这里注意符号要为'>'
}
};
然后开始迪杰斯特拉算法实现过程:
priority_queue<node>q;
//优先队列,其实这里一般使用一个pair,但为了方便理解所以用的结构体
inline void add(int u,int v,int w)
{
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];//存储该点的下一条边
head[u]=cnt;//更新目前该点的最后一条边(就是这一条边)
cnt++
}
//链式前向星加边
void dijkstra(){
//初始化
for(int i=1;i<=n;i++){
dis[i]=INF;
}
dis[s]=0;
q.push((node){0,s});
while(!q.empty()){//为队列空即为所有点都更新
node x=q.top();
q.pop();
int u=x.now;//记录队首(队列最小的边)并将其弹出
if(vis[u]) continue; //没有遍历过才需要遍历
vis[u]=1;
for(int i=head[u];i;i=e[i].next){//搜索队首所有连边
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;//松弛操作
q.push((node){dis[v],v});//把新遍历到的点加入堆中
}
}
}
}
最后定义主函数:
int main(){
cin>>n>>m>>s;//输入
int x,y,z;
for(int i=1;i<=m;i++)
{
cin>>x>>y>z;//输入起点,终点,权值
add(x,y,z);
}
dijkstra();//运行迪杰斯特拉算法
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);//输出权值
}
return 0;
}