浅谈单源最短路径

Nemlit

2017-12-18 15:13:32

Solution

[原文地址](https://www.cnblogs.com/bcoier/p/10363284.html) 这里给大家介绍三种最短路常用算法: floyd(O(n^3))、dijkstra(O(nlogn))、SPFA(O(KE)) 其实还有一个Bellman-Ford(O(nm))算法,但由于不常用而且SPFA是这个算法的改进版本,在这里就不列举了 # floyd:效率较低,只有70分 具体思路:将所有节点的距离都存在一个数组里,由于要枚举所有的两两组合以及每一个组合的“中转点”,再进行松弛操作 在求单源最短路径的时候就会浪费许多空间,但在求多源最短路时,复杂度仍是O(n^3)使用很广 ``` #include<bits/stdc++.h> using namespace std; #define inf 1234567890 #define maxn 10005 inline int read() { int x=0,k=1; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*k; }//快读 int a[maxn][maxn],n,m,s; inline void floyd() { for(int k=1;k<=n;k++) //这里要先枚举k(可以理解为中转点) { for(int i=1;i<=n;i++) { if(i==k||a[i][k]==inf) { continue; } for(int j=1;j<=n;j++) { a[i][j]=min(a[i][j],a[i][k]+a[k][j]); //松弛操作,即更新每两个点之间的距离 //松弛操作有三角形的三边关系推出 //即两边之和大于第三边 } } } } int main() { n=read(),m=read(),s=read(); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { a[i][j]=inf; } } //初始化,相当于memset(a,inf,sizeof(a)) for(int i=1,u,v,w;i<=m;i++) { u=read(),v=read(),w=read(); a[u][v]=min(a[u][v],w); //取min可以对付重边 } floyd(); a[s][s]=0; for(int i=1;i<=n;i++) { printf("%d ",a[s][i]); } return 0; } ``` # dijkstra:对于无负边的情况下可以达到O(nlogn)且很难被卡 具体思路:Dijkstra是基于一种贪心的策略,首先用数组dis记录起点到每个结点的最短路径,再用一个数组保存已经找到最短路径的点 然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点记为已经找到最短路 此时完成一个顶点,再看这个点能否到达其它点(记为v),将dis[v]的值进行更新 不断重复上述动作,将所有的点都更新到最短路径 这种算法实际上是O(n^2)的时间复杂度,但我们发现在dis数组中选择最小值时,我们可以用一些数据结构来进行优化。~~线段树?平衡树?~~ 其实我们可以用STL里的堆来进行优化,堆相对于线段树以及平衡树有着常数小,码量小等优点,并且堆的一个妙妙的性质就是可以在nlogn的时限内满足堆顶是堆内元素的最大(小)值,之不正是我们要的嘛? ``` 以下是用堆优化dijkstra代码: #include<bits/stdc++.h> using namespace std; #define maxn 10005 #define maxm 500005 #define INF 1234567890 inline int read() { int x=0,k=1; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*k; } struct Edge { int u,v,w,next; }e[maxm]; int head[maxn],cnt,n,m,s,vis[maxn],dis[maxn]; struct node { int w,now; inline bool operator <(const node &x)const //重载运算符把最小的元素放在堆顶(大根堆) { return w>x.w;//这里注意符号要为'>' } }; priority_queue<node>q; //优先队列,其实这里一般使用一个pair,但为了方便理解所以用的结构体 inline void add(int u,int v,int w) { e[++cnt].u=u; //这句话对于此题不需要,但在缩点之类的问题还是有用的 e[cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; //存储该点的下一条边 head[u]=cnt; //更新目前该点的最后一条边(就是这一条边) } //链式前向星加边 void dijkstra() { for(int i=1;i<=n;i++) { dis[i]=INF; } dis[s]=0; //赋初值 q.push((node){0,s}); while(!q.empty()) //堆为空即为所有点都更新 { node x=q.top(); q.pop(); int u=x.now; //记录堆顶(堆内最小的边)并将其弹出 if(vis[u]) continue; //没有遍历过才需要遍历 vis[u]=1; for(int i=head[u];i;i=e[i].next) //搜索堆顶所有连边 { int v=e[i].v; if(dis[v]>dis[u]+e[i].w) { dis[v]=dis[u]+e[i].w; //松弛操作 q.push((node){dis[v],v}); //把新遍历到的点加入堆中 } } } } int main() { n=read(),m=read(),s=read(); for(int i=1,x,y,z;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z); } dijkstra(); for(int i=1;i<=n;i++) { printf("%d ",dis[i]); } return 0; } ``` # SPFA:考场慎用,在毒瘤数据面前可能退化到O(nm) 具体思路:这里用的是STL队列,首先用数组dis记录起点到每个结点的最短路径,用邻接表来存储图,用vis数组记录当前节点是否在队列中 具体操作为:用队列来保存待优化的结点(类似于BFS),优化时每次取出队首结点,并且用队手节点来对最短路径进行更新并进行松弛操作 如果要对所连点的最短路径需要更新,且改点不在当前的队列中,就将改点加入队列 然后不断进行松弛操作,直至队列空为止。 ```cpp #include<bits/stdc++.h> using namespace std; inline int read() { int x=0,k=1; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*k; } #define maxn 10005 #define maxm 500005 #define inf 1234567890 int n,m,s,tot,dis[maxn],head[maxn]; bool vis[maxn]; struct Edge { int next,to,w; }h[maxm]; void add(int u,int v,int w) { h[++tot].next=head[u]; h[tot].to=v; h[tot].w=w; head[u]=tot; } //上面和dijkstra算法基本上一样 queue<int> q; //队列优化 inline void spfa() { for(int i=1; i<=n; i++) { dis[i]=inf; //赋初值 } int u,v; q.push(s); dis[s]=0; //将起点的值负为0 vis[s]=1;//这句话可加可不加,因为循环的时候vis[s]又会被赋为0 while(!q.empty()) //当队列里没有元素的时候,那就已经更新了所有的单源最短路径 { u=q.front(); //将队手节点记录并弹出队首节点 q.pop(); vis[u]=0; for(int i=head[u];i;i=h[i].next) //寻找与u相连的边 { v=h[i].to; if(dis[v]>dis[u]+h[i].w) { dis[v]=dis[u]+h[i].w; //松弛操作,和floyd比较相似 if(!vis[v]) { //已经在队列里的点就不用再进入了 vis[v]=1; q.push(v); } } } } } int main(){ n=read(),m=read(),s=read(); for(int i=1,u,v,w;i<=m;i++) { u=read(),v=read(),w=read(); add(u,v,w); } spfa(); for(int i=1; i<=n; i++) { printf("%d ",dis[i]); } return 0; } ```