1.Dijkstra人物简介
艾兹格·W·迪科斯彻(Edsger Wybe Dijkstra,1930年5月11日~2002年8月6日),计算机科学家,1972年图灵奖获得者,于1959年提出了Dijkstra算法;
2.Dijkstra算法
适用范围:
1.单源最短路径
2.边权非负图
Dijkstra算法基于贪心的思想,dist[x]已经是起点到x的最短路径,我们不断用全局最小值进行标记和扩展,最终可得到起点到每个节点的最短路径长度。
Dijkstra算法证明:
证明略,可参考其他博客
ps:其实我不会( ̄﹏ ̄ )
算法流程:
1.初始化dist[1] = 0,其余节点为正无穷大
2.找出一个未被标记的、dist[x]最小的节点x,然后标记节点x
3.扫描节点x的所有出边(x,y,z),若dist[y] >dist[x] + z,则使用dist[x] + z 更新dist[y]
4.重复上述2~3两个步骤,直到所有节点被标记。
流程图:
1.初始化dist[1] = 0,其余节点为正无穷大
2.dist数组中未被标记的、值最小的,是1号节点,将其标记
3.扫描1号节点的所有出边,由于dist[3]>dist[1] + 1,说明通过1号节点走一段长度为1的路可以到达3号节点,并且比当前dist[3]中的存储的路要短,所以将dist3更新,则dist[3] = 1;
由于dist[2] > dist[1] + 7, 则 dist[2] = 7;
4.(2)dist数组中未被标记的、值最小的,是3号节点,将其标记;
5.(3)扫描3号节点的所有出边,
由于dist[2]>dist[3] + 2,则 dist[2] = 3;
由于dist[4]>dist[3] + 5,则 dist[4] = 6;
由于dist[6]>dist[3] + 4,则 dist[6] = 5;将其更新;
6.(2)dist数组中未被标记的、值最小的,是2号节点,将其标记;
7.(3)扫描2号节点的所有出边,
由于dist[4] < dist[2] + 8,则本次不更新;
8.(2)dist数组中未被标记的、值最小的,是6号节点,将其标记;
9.(3)扫描2号节点的所有出边,
由于dist[4] < dist[6] + 10,则本次不更新;
10.(2)dist数组中未被标记的、值最小的,是4号节点,将其标记;
11.(3)扫描4号节点的所有出边,
由于dist[5] > dist[4] + 2,则 dist[5] = 8;
12.(2)dist数组中未被标记的、值最小的,是5号节点,将其标记;
13.(3)扫描5号节点的所有出边,
由于dist[6] < dist[5] + 6,则本次不更新;
14.所有节点被标记,算法完成!
3.Dijkstra算法模板——基础版
dist[i] : 表示从1到i的最短路径为dist[i]
v[i] = ture:表示 i 节点已被标记,false为未标记
A[i][j]:邻接矩阵,表示i节点到j节点的路径长度为A[i][j]
时间复杂度:O(n²)
void dijkstra() {
memset(dist,0x3f, sizeof(dist)); // dist数组,初始化为无穷大
memset(v, 0, sizeof(v) );//节点标记数组初始化
d[1] = 0;//将源点的距离初始化为0
for (int i = 1; i < n; i++) { //重复进行n-1次
int x = 0;
//找到未标记节点中dist值最小的节点号,并记录到x中
for (int j = 1; j<= n;j++)
if (!v[j] && (x == 0 || d[j] < d[x]))
x = j;
v[x] = 1;//将其标记
//用全局最小值点×更新其他节点
for (int y = 1; y <= n; y++)
d[y] = min(d[y], d[x] + A[x][y]);
}
}
4.Dijkstra算法模板——堆优化版
上述代码时间复杂度为O(n²),主要瓶颈就在于寻找全局最小值的过程,在上述代码中,寻找全局最小值时,循环了n次,此处可以用二叉堆(priority_queue)对dist数组进行维护,用O(log n)的时间找到最小值,执行一条边的扩展和更新,最终在O(mlog n)的时间内实现Dijkstra算法.(m条边,n个节点)
priority_queue< pair<int ,int> > q;
void add(int x,int y,int z)//邻接表存储图
{
ver[++tot]=y;edge[tot]=z;
Next[tot]=head[x];head[x]=tot;
}
void dijkstra(){
memset(dist,0x3f,sizeof dist);
memset(v,0,sizeof v);
dist[1]=0;
q.push(make_pair(0,1));
while(q.size()){
//取出堆顶
int x=q.top().second;
q.pop();
if(v[x]) continue;
v[x]=1;
//扫描所有出边
for(int i=head[x];i;i=Next[i])
{
int y=ver[i],z=edge[i];
if(dist[y]>dist[x]+z){
dist[y]=dist[x]+z;
q.push(make_pair(-dist[y],y));//加入堆
}
}
}
}
注意:为什么加入堆时用-dist[y]呢? 由于priority_queue默认是从大到小的顺序排列的,故将所有dist取负数时,堆顶的dist是最小的。也可以自定义比较函数使得该堆从小到大排列:
priority_queue<int, vector< int >, greater< int > > q
5.参考
《算法竞赛进阶指南》——李煜东