(图论)Dijkstra算法及其堆优化的写法和应用
Dijkstra(以下简称为dij),是最短路算法中最为重要的算法(相比于O(n3)的弗洛伊德和莫名死掉的SPFA),dij的优点在于快且大多数情况下比较稳定(尤其是单源最短路)。
以下是有关图解
如图,一开始,所有的点均为圆点,此时dis[1]=0,其余为MAX_INT
第一轮找到dis[1]最小,所以将1标为方点,得到新的dis[2]=2,dis[3]=4,dis[4]=7
第二轮找到dis[2]最小,所以将2标为方点,得到新的dis[3]=3,dis[5]=4
第三轮找到dis[3]最小,所以将3标为白点,得到新的dis[4]=4
最后两轮循环再将点4和点5变成白点即可。
所以最后dis数组
i 1 2 3 4 5
dis[i] 0 2 3 4 4
例题:
题目描述
给定一个 N 个点,M 条有向边的带非负权图,请你计算从 S 出发,到每个点的距离。
数据保证你能从 S 出发到任意点。
输入格式
第一行为三个正整数 N, M, SN,M,S。 第二行起 MM 行,每行三个非负整数 ui, vi, wi,表示从 ui到 vi有一条权值为 wi的边。
输出格式
输出一行 N 个空格分隔的非负整数,表示 S 到每个点的距离。
输入输出样例
输入 #1 输出 #1
4 6 1 0 2 4 3
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
说明/提示
1≤N≤100000;
1≤M≤200000;
S=1;
1≤ui,vi≤N;
wi≤109;
∑wi≤109;
乍一看,有固定的的起点和终点,dij是个不错的选择,小蒟蒻偷笑。
于是小蒟蒻开始动手写dij,代码如下(朴素的dij,本题仅供参考)
int s=begin;
dis[s]=0;
while(b[s]){
int minn=2147483647;
b[s]=false;
for(int i=pre[s];i!=0;i=ed[i].en){
if(b[ed[i].be]&&dis[ed[i].be]>dis[s]+ed[i].w)
dis[ed[i].be]=dis[s]+ed[i].w;
}
for(int i=1;i<=n;i++){
if(dis[i]<minn&&b[i]){
minn=dis[i];
s=i;
}
}
确实没毛病,毕竟是写(chao)了(guo)这么多遍的代码,写错了岂不是很尴尬。于是乎我兴致高昂的提交了这份代码(当然这不是完整代码),等待评测姬的回复。过了大概十几秒,评测姬告诉我:TLE(超时),而且十个测试点,每一个都超时。果然还是小蒟蒻,写的代码都那么弱。
那还有什么改进方案呢,这个数据范围,弗洛伊德和 死去的 SPFA肯定是不行的了,那还能怎么办???
不急,我们的主角还没有登场呢:堆优化。
堆优化的本质就是用一个优先队列维护当前所有点离源点的距离,每次弹出最短的,直到出现圆点为止。这样就省去了O(n)的寻找,总时间复杂度O(n+mlogn)(优先队列的复杂度为mlogn)。 至于一些dalao说的稠密图,蒟蒻在这里暂且不考虑了(手动滑稽)。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#define MAX 0x7fffffff
using namespace std;
inline int read() {//读入优化
int X=0,w=1; char c=getchar();
while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
return X*w;
}
struct edge{
int son,bro,w;
}ed[200005];//边
int pre[100005],temp;
inline void add(int a,int b,int c){//建图
ed[++temp].son=b;
ed[temp].w=c;
ed[temp].bro=pre[a];//兄弟节点的位置
pre[a]=temp;
}
int dis[100005],s;
struct node{
int dis,pos;//pos点的位置 ,dis到该点的最短距离
bool operator <(const node &a)const{//重载小于号,用于优先队列
return a.dis<dis;
}
};
priority_queue<node> que;//优先队列,堆
int n,m;
bool b[100005];//判断是否走过
int begin;
inline void work(){
s=begin;
dis[s]=0;//第一个点到第一个点的距离为零
que.push((node){0,s});
while(!que.empty()){
node temp=que.top();
que.pop();
s=temp.pos;
if(b[s]) continue;//走过,这句是优化的关键
b[s]=true;//走过了
for(register int i=pre[s];i;i=ed[i].bro){//访问兄弟节点
int y=ed[i].son;//子节点的位置
if(dis[y]>dis[s]+ed[i].w){
dis[y]=dis[s]+ed[i].w;//最短路
if(!b[y])//没走过
que.push((node){dis[y],y});//入队
}
}
}
}
int main(){
n=read();m=read();begin=read();
for(int i=1;i<=n;i++) dis[i]=MAX;//置为最大值
memset(b,false,sizeof(b));//没有访问过
int x,y,v;
for(int i=1;i<=m;i++){
x=read();y=read();v=read();
add(x,y,v);
//add(y,x,v)如果是无向图的话,此题为有向图
}
work();//进行dij 主程序
for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
return 0;
}
这是蒟蒻第一次写解题报告,希望dalao们勿喷鸭。