算法
算法简介
d i j k s t r a dijkstra dijkstra是单源最短路径的有效算法,解决的是有向图中最短路径问题。dijkstra主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
算法原理
- d i j k s t r a dijkstra dijkstra本质上的思想是贪心,它只适用于不含负权边的图。
- 我们把点分成两类,一类是已经确定最短路径的点(即已经访问过/未标记占领的点),另一类是未确定最短路径的点(即还未访问过的点/未标记占领的点)。
- 初始化将源点到各个节点i的最短路径 d i s [ i ] dis[i] dis[i]设为最大值,同时将源点s的最短路径 d i s [ s ] dis[s] dis[s]设为 0 0 0,并将其放入节点集合 S S S中。
- 在节点集合 S S S中未确定最短路径的点里找到 d i s dis dis最小的点 u u u,并标记占领。
- 遍历u的所有出边 ( u , v , w ) (u,v,w) (u,v,w)若 d i s [ v ] > d i s [ u ] + w dis[v] > dis[u] + w dis[v]>dis[u]+w,则令 d i s [ v ] = d i s [ u ] + w dis[v] = dis[u] + w dis[v]=dis[u]+w,并将节点v放入节点集合S中。
- 重复 4 4 4, 5 5 5步操作,直至所以点都被标记占领。
说明
1. 为什么dijkstra是正确的?
当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新。所以在上述算法原理第
4
4
4步中找出的未标记占领的点
u
u
u必然满足
u
:
d
i
s
[
u
]
u:dis[u]
u:dis[u]已经是源点到
u
u
u的最短路径。我们不断选择全局最小值进行标记和拓展,最终可以得到起点到每个节点的最短路径的长度。
下面用以下这个例子来简单说明:
将源点
s
s
s设为
1
1
1。开始时我们把
d
i
s
[
s
]
dis[s]
dis[s]初始化为
0
0
0,其余点初始化为极大值
I
N
F
INF
INF。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
dis | INF | INF | INF | INF | INF |
used | false | false | false | false | false |
第一轮循环找到
d
i
s
dis
dis值最小的点
1
1
1,将1标记,对所有与1相连的未标记的点的dis值进行维护,使得
d
i
s
[
2
]
=
2
dis[2]=2
dis[2]=2,
d
i
s
[
3
]
=
4
dis[3]=4
dis[3]=4,
d
i
s
[
4
]
=
7
dis[4]=7
dis[4]=7。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
dis | 0 | 2 | 4 | 7 | INF |
used | true | false | false | false | false |
第二轮循环找到
d
i
s
dis
dis值最小的点
2
2
2,将
2
2
2标记,对所有与
2
2
2相连的未标记点的dis值进行维护,使得
d
i
s
[
3
]
=
3
dis[3]=3
dis[3]=3,
d
i
s
[
5
]
=
4
dis[5]=4
dis[5]=4。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
dis | 0 | 2 | 3 | 7 | 4 |
used | true | true | false | false | false |
第三轮循环找到
d
i
s
dis
dis值最小的点
3
3
3,将
3
3
3标记,对所有与
2
2
2相连的未标记的点的
d
i
s
dis
dis值进行维护,使得
d
i
s
[
4
]
=
5
dis[4]=5
dis[4]=5。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
dis | 0 | 2 | 3 | 5 | 4 |
used | true | true | true | false | false |
接下来两轮循环分别将
5
5
5,
4
4
4标记,算法结束,已求出所有点的最短路径。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
dis | 0 | 2 | 3 | 5 | 4 |
used | true | true | true | true | true |
算法时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
2. dijkstra为什么不能处理有负权边的情况?
d
i
j
k
s
t
r
a
dijkstra
dijkstra本质上的思想是贪心。在运行过程中维持的关键信息是一组节点集合
S
S
S,从源节点
s
s
s到该集合中每个节点之间的最短路径已经被找到。算法重复从节点集合
S
S
S中选择最短路径估计最小的节点
u
u
u,将
u
u
u加入到集合
S
S
S,然后对所有从
u
u
u出发的边进行松弛操作。
当把一个节点选入集合
S
S
S时,即意味着已经找到了从源点到这个点的最短路径,但若存在负权边,就与这个前提矛盾,可能会出现得出的距离加上负权后比已经得到
S
S
S中的最短路径还短。
下面用以下这个例子来简单说明:
用
d
i
j
k
s
t
r
a
dijkstra
dijkstra求得
d
i
s
[
1
]
[
2
]
=
3
dis[1][2]=3
dis[1][2]=3,事实上
d
[
1
]
[
2
]
=
2
d[1][2]=2
d[1][2]=2,就是通过了
1
→
3
→
2
1\rightarrow3\rightarrow2
1→3→2使得路径减小。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int MAXN=10005;
const int MAXM=500005;
int n,m,s;
int dis[MAXN];
bool used[MAXN];
int size=0;
int head[MAXN];
struct E
{
int u;
int v;
int w;
int next;
}edge[MAXM];
inline int read()
{
int X=0; bool flag=1;
char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
inline void add(int u,int v,int w)
{
edge[size].u=u;
edge[size].v=v;
edge[size].w=w;
edge[size].next=head[u];
head[u]=size++;
}
struct node
{
int dis;
int pos;
bool operator <(const node &x) const
{
return x.dis<dis;//inportant
}
};
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 u=tmp.pos, d=tmp.dis;
if(used[u]) continue;
used[u]=1;
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].v;
if(dis[v]>dis[u]+edge[i].w)
{
dis[v]=dis[u]+edge[i].w;
if(!used[v])
{
q.push((node){dis[v],v});
}
}
}
}
}
int main()
{
freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read();m=read(); s=read();
for(int i=1;i<=n;++i) dis[i]=0x7fffffff;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
add(u,v,w);
}
dijkstra();
for(int i=1;i<=n;i++)
printf("%d ",dis[i]);
return 0;
}
经典例题
蒟蒻学艺不精,还望大佬指点。