dijkstra
前言
S P F A " role="presentation">算法由于它上限O ( N M ) = O ( V E ) " role="presentation"> 的时间复杂度,被卡掉的几率很大.在算法竞赛中,我们需要一个更稳定的算法:d i j k s t r a " role="presentation"> .
什么是d i j k s t r a " role="presentation"> ?
d i j k s t r a " role="presentation"> 是一种单源最短路径算法,时间复杂度上限为O ( n 2 ) " role="presentation"> (朴素),在实际应用中较为稳定; " role="presentation"> 加上堆优化之后更是具有O ( ( n + m ) log 2 ⁡ n ) " role="presentation"> 的时间复杂度,在稠密图中有不俗的表现.
d i j k s t r a " role="presentation"> 的原理/流程?
d i j k s t r a " role="presentation"> 本质上的思想是贪心,它只适用于不含负权边的图.d i j k s t r a " role="presentation"> 的流程如下: " role="presentation">1. " role="presentation"> 初始化d i s [ s t a r t ] = 0 , " role="presentation"> 其余节点的d i s " role="presentation"> 值为无穷大.2. " role="presentation"> 找一个未被标记的,d i s " role="presentation"> 值最小的节点x , " role="presentation"> 标记节点x . " role="presentation">3. " role="presentation"> 遍历x " role="presentation"> 的所有出边( x , y , z ) , " role="presentation"> 若d i s [ y ] > d i s [ x ] + z , " role="presentation"> 则令d i s [ y ] = d i s [ x ] + z " role="presentation">4. " role="presentation"> 重复2 , 3 " role="presentation"> 两步,直到所有点都被标记. " role="presentation">- 时间复杂度为
O ( n 2 ) " role="presentation">
图解
- 我们把点分成两类,一类是已经确定最短路径的点,称为”白点”,另一类是未确定最短路径的点,称为”蓝点”
- (令
s t a r t = 1 " role="presentation"> ) - 开始时我们把
d i s [ s t a r t ] " role="presentation"> 初始化为0 " role="presentation"> ,其余点初始化为i n f " role="presentation"> - 第一轮循环找到
d i s " role="presentation"> 值最小的点1 " role="presentation"> ,将1 " role="presentation"> 变成白点,对所有与1 " role="presentation"> 相连的蓝点的d i s " role="presentation"> 值进行修改,使得d i s [ 2 ] = 2 , d i s [ 3 ] = 4 , d i s [ 4 ] = 7 " role="presentation"> - 第二轮循环找到
d i s " role="presentation"> 值最小的点2 " role="presentation"> ,将2 " role="presentation"> 变成白点,对所有与2 " role="presentation"> 相连的蓝点的d i s " role="presentation"> 值进行修改,使得d i s [ 3 ] = 3 , d i s [ 5 ] = 4 " role="presentation"> - 第三轮循环找到
d i s " role="presentation"> 值最小的点3 " role="presentation"> ,将3 " role="presentation"> 变成白点,对所有与2 " role="presentation"> 相连的蓝点的d i s " role="presentation"> 值进行修改,使得d i s [ 4 ] = 4 " role="presentation"> - 接下来两轮循环分别将
4 , 5 " role="presentation"> 设为白点,算法结束,求出所有点的最短路径 - 时间复杂度
O ( n 2 ) " role="presentation">
为什么d i j k s t r a " role="presentation"> 不能处理有负权边的情况?
- 我们来看下面这张图
2 " role="presentation"> 到3 " role="presentation"> 的边权为− 4 " role="presentation"> ,显然从1 " role="presentation"> 到3 " role="presentation"> 的最短路径为− 2 " role="presentation">( 1 − > 2 − > 3 ) . " role="presentation"> 但在循环开始时程序会找到当前d i s " role="presentation"> 值最小的点3 " role="presentation"> ,并标记它为白点.- 这时的
d i s [ 3 ] = 1 , " role="presentation"> 然而1 " role="presentation">并不是起点到3 " role="presentation"> 的最短路径.因为3 " role="presentation"> 已经被标为白点,所以d i s [ 3 ] " role="presentation"> 不会再被修改了.我们在边权存在负数的情况下得到了错误的答案.
d i j k s t r a " role="presentation"> 的堆优化?
- 观察
d i j k s t r a " role="presentation"> 的流程,发现步骤2 " role="presentation"> 可以优化 - 怎么优化呢?
我会zkw线段树!我会斐波那契堆!- 我会堆!
我们可以用堆对
d i s " role="presentation">数组进行维护,用O ( log 2 ⁡ n ) " role="presentation"> 的时间取出堆顶元素并删除,用O ( log 2 ⁡ n ) " role="presentation"> 遍历每条边,总复杂度O ( ( n + m ) log 2 ⁡ n ) " role="presentation">范例代码:
#include<bits/stdc++.h>
const int MaxN = 100010, MaxM = 500010;
struct edge
{
int to, dis, next;
};
edge e[MaxM];
int head[MaxN], dis[MaxN], cnt;
bool vis[MaxN];
int n, m, s;
inline void add_edge( int u, int v, int d )
{
cnt++;
e[cnt].dis = d;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
struct node
{
int dis;
int pos;
bool operator <( const node &x )const
{
return x.dis < dis;
}
};
std::priority_queue<node> q;
inline void dijkstra()
{
dis[s] = 0;
q.push( ( node ){0, s} );
while( !q.empty() )
{
node tmp = q.top();
q.pop();
int x = tmp.pos, d = tmp.dis;
if( vis[x] )
continue;
vis[x] = 1;
for( int i = head[x]; i; i = e[i].next )
{
int y = e[i].to;
if( dis[y] > dis[x] + e[i].dis )
{
dis[y] = dis[x] + e[i].dis;
if( !vis[y] )
{
q.push( ( node ){dis[y], y} );
}
}
}
}
}
int main()
{
scanf( "%d%d%d", &n, &m, &s );
for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
for( register int i = 0; i < m; ++i )
{
register int u, v, d;
scanf( "%d%d%d", &u, &v, &d );
add_edge( u, v, d );
}
dijkstra();
for( int i = 1; i <= n; i++ )
printf( "%d ", dis[i] );
return 0;
}
例题
- 入门模板:P3371
- 进阶模板:P4779
- 其余例题请右转洛谷题库,搜索”最短路”
后记
- 本文部分内容摘自李煜东《算法竞赛进阶指南》和《信息学竞赛一本通》
- 友情提示:正权图请使用
d i j k s t r a " role="presentation"> 算法,负权图请使用S P F A " role="presentation"> 算法
相关阅读
迪杰斯特拉算法可以用来求图的最短路径,本文通过对一个无向图最短路径的求取问题来讲解迪杰斯特拉算法。假如有无向图如下: 首先我