最短路
图上最短路径算法分为单源最短路、多源最短路,单源常见算法:Bellman-ford、SPFA、Dijkstra及其堆优化;多源常见算法:Floyd。
单源最短路
Bellman-ford
记 \(dis_u\) 为源点到 \(u\) 当前的最短路。循环,每一次循环:枚举每一条边,尝试利用这一条边和边连接的两个节点中一个已经确定其最短路径的节点的 \(dis\) 来松弛另一个节点的 \(dis\)。一共经过 \(n-1\) 次这样的对所有边的松弛操作可以保证所有 \(dis\) 确定。(具有判断负权环的功能)的如果此时再循环一次发现还有边可以被松弛,那么说明,一定存在负权环。
时间复杂度:\(O(n\cdot m)\)
模板:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5,M=1e6,inf=1e9;
int dis[N],u[M],v[M],w[M];
int main()
{
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
//u[m+i]=v[i],v[m+i]=u[i],w[m+i]=i;
}
for(int i=1;i<=n;i++) dis[i]=inf;
dis[s]=0;
for(int i=1;i<=n-1;i++)
for(int j=1;j<=m;j++)
if(dis[v[j]]>dis[u[j]]+w[j])
dis[v[j]]=dis[u[j]]+w[j];
bool flag=true;
for(int i=1;i<=m;i++)
if(dis[v[i]]>dis[u[i]]+w[i]){
flag=false;
break;
}
if(flag) for(int i=1;i<=n;i++) cout<<dis[i]<<endl;
else puts("-1");
return 0;
}
SPFA
一说 SPFA 是 Bellman-ford 的优化,一说可以理解成不同思想的两个独立算法。
首先将源点放入队列,当队列不为空时循环;每次循环取出队首记为 \(now\),把 \(now\) 的所有相连的节点遍历一遍,遍历到点 \(x\),判断 \(dis_x\) 是否大于 \(dis_{now}+e_{now,x}\),即松弛。减少了 Bellman-ford 无意义的尝试,复杂度 \(O(k\cdot m)\),其中 \(k\) 为常数且一般不超过 \(4\)。注意当出现负权环时队列永远不可能为空,即死循环。利用 Bellman-ford 判断负权环是否存在。
模板:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5,inf=1e9;
struct type {
int node,edge;
}t;
vector<type> G[N];
queue<int> Q;
int dis[N],inque[N];
int main()
{
int n,m,s,u,v,w;
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
t.node=v,t.edge=w,G[u].push_back(t);
//t.node=u,t.edge=w,G[v].push_back(t);
}
for(int i=1;i<=n;i++) dis[i]=inf;
Q.push(s);
dis[s]=0,inque[s]=1;
while(!Q.empty()){
int now=Q.front();
Q.pop(),inque[now]=0;
for(int i=0;i<G[now].size();i++)
if(dis[G[now][i].node]>dis[now]+G[now][i].edge){
dis[G[now][i].node]=dis[now]+G[now][i].edge;
Q.push(G[now][i].node);
inque[G[now][i].node]=1;
}
}
for(int i=1;i<=n;i++) cout<<dis[i]<<endl;
return 0;
}
Dijkstra 朴素
同样考虑松弛。记源点为 \(s\)。初始时 \(dis_s\) 已定,\(dis_i\) 设为 \(e_{s,i}\)。剩下的 \(n-1\) 个还要经过松弛判断是否确定的 \(dis\),所以进行 \(n-1\) 次循环。每次循环:从当前所有的 \(dis\) 中找到最小的一个 \(dis_x\),可知 \(dis_x\) 为定值;尝试对各点的 \(dis\) 用 \(x\) 松弛,即用 \(dis_x+e_{x,j}\) 尝试松弛 \(dis_j\)(\(j=1,2,\ldots,n\))。最后得到的 \(dis\) 数组中所有值皆确定。
时间复杂度:\(O(n^2)\)
模板:【略】
Dijkstra 堆优化
void dij(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
Q.push(make_pair(0,s));
while(!Q.empty()){
int x=Q.top().second;
if(inq[x])continue;
Q.pop(),inq[x]=1;
for(int j=0;j<G[x].size();j++){
int y=G[x][j].first,z=G[x][j].second;
if(dis[y]>dis[x]+z){
dis[y]=dis[x]+z;
Q.push(make_pair(-dis[y],y));
}
}
}
}
多源最短路
Floyd
Floyd 可以求得各点之间的最短路。对于 \(e_{i,j}\) 考虑通过 \(k\) 来松弛,即如果 \(e_{i,j}>e_{i,k}+e_{k,j}\) 则松弛 \(e_{i,j}\)。\(k,i,j\) 分别从 \(1\sim n\) 循环,循环层序不能颠倒,复杂度 \(O(n^3)\)。
Floyd 可以判断一点是否可以到达另一点。如果原来就可以 \(i\) 到达 \(j\),或者通过 \(k\) 中转存在 \(i\to k\) 可达且 \(k\to j\) 可达那么更新 \(e_{i,j}\)。
模板(核心5行):
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j]) //松弛,最短路
e[i][j]=e[i][k]+e[k][j];
/*
e[i][j]=e[i][j]|e[i][k]&e[k][j]; //判断是否i→j可达
*/
单源次短路
#include <bits/stdc++.h>
using namespace std;
const int N=5005,INF=0x3f3f3f3f;
int n,m,dis1[N],dis2[N];
vector<pair<int,int> >G[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
G[u].push_back(make_pair(v,w)),G[v].push_back(make_pair(u,w));
}
memset(dis1,0x3f,sizeof(dis1));memset(dis2,0x3f,sizeof(dis2));
dis1[1]=0;
priority_queue<pair<int,int> >Q;
Q.push(make_pair(0,1));
while(!Q.empty()){
int x=Q.top().second;Q.pop();
for(int i=0;i<G[x].size();i++){
int y=G[x][i].first,z=G[x][i].second;
if(dis1[y]>dis1[x]+z){
dis2[y]=dis1[y];
dis1[y]=dis1[x]+z;
if(dis2[x]+z<dis2[y]&&dis2[x]+z!=dis1[y])dis2[y]=dis2[x]+z;
Q.push(make_pair(-dis1[y],y));
}
else if(dis1[x]+z!=dis1[y]&&dis1[x]+z<dis2[y]){
dis2[y]=dis1[x]+z;
Q.push(make_pair(-dis1[y],y));
}
else if(dis2[x]+z!=dis1[y]&&dis2[x]+z<dis2[y]){
dis2[y]=dis2[x]+z;
Q.push(make_pair(-dis1[y],y));
}
}
}
cout<<dis2[n];
}