Dijkstra算法优化
关于Dijkstra
详见图论初步——最短路(1)
前面的文章中,我们提到了
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法的核心就是每次找到剩余的点中的最小值。
这一步用了一个
f
o
r
for
for循环,很花时间。
所以如何快速找到剩下的点的最小值,就是我们要解决的问题。
方法1:线段树
每次找到最小值及其编号,然后赋为极大值,以不影响后面取最小值。
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)单次,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)建树,总复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
代码复杂度:难
方法2:分块
操作方法同线段树
时间复杂度:单次
O
(
n
)
O(\sqrt n)
O(n),总复杂度
O
(
n
n
)
O(n\sqrt n)
O(nn)
代码复杂度:中
方法3:堆
建立小根堆,找到最小值后弹出堆,更新距离时在堆里修改。
时间复杂度:单次操作
O
(
l
o
g
n
)
O(logn)
O(logn),总复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
代码复杂度:中
方法3.5:优先队列
从方法3得到提示,我们可以用
S
T
L
STL
STL里面的priority_queue,即优先队列
时间复杂度:同堆
代码复杂度:简单
优先队列实现方式
由于要同时记录距离及编号,所以我们考虑使用 S T L STL STL中的make_pair函数。在优先队列中自动按第一关键字排序。
细节
由于c++优先队列默认为大根堆,而我们需要小根堆,故有2种操作方式:
第一种(不要轻易尝试):
#define pair<int,int> pii
priority_queue<pii,vector<pii>,greater<pii> >;
第二种:插入时插入负数,因为我们只需要点的具体编号,距离只做比较用。
代码
#include<bits/stdc++.h>
using namespace std;
int first[200005],Next[200005],to[200005],w[200005],tot=0,d[200005];
bool vis[200005];
int Read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void Print(int x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9) Print(x/10);
putchar(x%10+'0');
}
void add(int x,int y,int z){
Next[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
priority_queue< pair<int,int> > q;
void dijkstra(int x){
memset(d,0x3f3f3f3f,sizeof(d));
memset(vis,false,sizeof(vis));
d[x]=0;
pair<int,int> p=make_pair(0,x);
q.push(p);
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=true;
int e,v;
for(e=first[u];e,v=to[e];e=Next[e]){
if(d[v]>d[u]+w[e]){
d[v]=d[u]+w[e];
q.push(make_pair(-d[v],v));
}
}
}
}
int main(){
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int x,y,z;
x=Read();
y=Read();
z=Read();
add(x,y,z);
}
dijkstra(s);
for(int i=1;i<=n;i++){
Print(d[i]);
putchar(' ');
}
}
spfa算法
缺点:容易被卡,但随机数据时很快
优点:可以处理负权情况
与
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra一个点标记后无法被再次更新相比,
s
p
f
a
spfa
spfa允许一个点多次被更新,这使它可以处理有负权的情况,与
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra优先队列不同,
s
p
f
a
spfa
spfa使用普通队列。
实现
先将起点放入队列,然后只要队列不空,就取出队首,用它来更新其余点,如果成功更新,就判断该点是否在队列里,如果不是,则让其入队,这样一直下去,直到队列为空。
拓展
当存在负环时,就不存在最短路了,这时spfa就会展现出其优点。在存在最短路的情况下,一个点最多被其余的点更新一次,也就是更新 n − 1 n-1 n−1次。所以记录该点入队的次数,如果达到 n n n次,就存在负环,跳出 s p f a spfa spfa过程。
代码
spfa:
void spfa()
{
queue<int> q;
for(int i=1;i<=n;i++)
{
d[i]=2147483647;
inq[i]=false;
}
q.push(s);
inq[s]=true;
d[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for(int i=head[u];i;i=next[i])
{
int v=to[i];
if(d[v]>d[u]+dis[i])
{
d[v]=d[u]+dis[i];
if(!inq[v])
{
q.push(v);
inq[v]=true;
}
}
}
}
}
负环:
bool spfa(int s)
{
queue<int> q;
for(int i=1;i<=n;i++)
{
d[i]=2147483647;
inq[i]=false;
cnt[i]=0;
}
q.push(s);
inq[s]=true;
d[s]=0;
cnt[s]++;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for(int i=head[u];i;i=next[i])
{
int v=to[i];
if(d[v]>d[u]+dis[i])
{
d[v]=d[u]+dis[i];
if(!inq[v])
{
q.push(v);
inq[v]=true;
cnt[v]++;
if(cnt[v]>n) return false;
}
}
}
}
return true;
}
顺便一提,想为一个数组赋特定值时可以使用fill函数,例如将 a [ 1 ] − a [ n ] a[1]-a[n] a[1]−a[n]赋为100000,可以
fill(a+1,a+n+1,100000);