(笔记)Dijkastra Bellman-Ford SPFA Floyd 最短路算法
Dijkstra
朴素版本:每次找到所有节点中没有找过的且 \(dis_u\) 最小的节点,然后以它为中心分别遍历 \(v\in 1\to n\),如果存在边就用 \(dis_u\) 更新 \(dis_v\),限制搜索次数,适用于稠密图,边数 \(m\) 为 \(O(n^2)\) 级别的,时间复杂度为 \(O(n^2)\)。
堆优化版本:利用堆去掉找最小 \(dis_u\) 节点的循环,每次 \(u\) 取堆顶即可,直接用链式前向星或者 vector 访问相邻节点 \(v\),每次成功更新一个节点 \(v\) 将其丢进堆里。适用于稀疏图,时间复杂度 \(O(m\log n)\)
缺点:不适用于负环(堆优化)。
点击查看代码
New version
v1:
typedef long long LL;
typedef pair<LL,int> PII;
const int N=1e5+5,M=5e5+5;
const LL INF=1e16;
struct Node{int v,next,w,u;};
LL dis[N];
int head[N],idx;
Node adj[M<<1];
void ins(int x,int y,int z){
adj[++idx].v=y;
adj[idx].u=x;
adj[idx].next=head[x];
adj[idx].w=z;
head[x]=idx;
}
void undo(){
if(!idx)return ;
head[adj[idx].u]=adj[idx].next;
idx--;
}
void init(){
idx=0;
for(int i=1;i<=n+2;i++)
head[i]=0;
}
priority_queue<PII,vector<PII>,greater<PII> >q;
void dijkstra(int st){
for(int i=1;i<=n+2;i++)dis[i]=INF;
dis[st]=0;q.push(make_pair(0,st));
while(!q.empty()){
int u=q.top().second;
LL d=q.top().first;
q.pop();
for(int i=head[u];i;i=adj[i].next){
int v=adj[i].v;
int w=adj[i].w;
if(d+w<dis[v])dis[v]=d+w,q.push(make_pair(dis[v],v));
}
}
}
int id[N];
v2:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1e6+5,INF=1e9;
int n,m,r,Q;
int head[N],idx;
struct Edge{
int v,next;
}adj[N<<1];
void ins(int x,int y){
adj[++idx].v=y;
adj[idx].next=head[x];
head[x]=idx;
}
int dis[N];
priority_queue<PII,vector<PII>,greater<PII> >q;
void dij(){
for(int i=1;i<=n;i++)
dis[i]=INF;
dis[r]=0;
q.push(make_pair(0,r));
while(!q.empty()){
int u=q.top().second;
q.pop();
for(int i=head[u];i;i=adj[i].next){
int v=adj[i].v;
if(dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
q.push(make_pair(dis[v],v));
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>r;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
ins(u,v);ins(v,u);
}
dij();
return 0;
}
Old version
#include<bits/stdc++.h>
using namespace std;
typedef pair<long long,long long>PII;
const int N = 2000005;
struct node{
long long v,w,next;
node(){
next=w=0;
}
node(long long a,long long b,long long c){
v=a;w=b;next=c;
}
};
node adj[N];
long long h[N],s;
long long idx=1;
long long dist[N];
bool st[N];
long long n,m;
void insert(long long x,long long y,long long z){
adj[idx]=node(y,z,h[x] );
h[x] = idx;
idx++;
}
void dijkstra(){
memset(dist, 0x3f3f3f3f, sizeof(dist));
dist[s] = 0;
priority_queue< PII, vector<PII>, greater<PII> > heap; // 定义一个小根堆
heap.push({ 0, s }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
while (heap.size())
{
PII k = heap.top() ; // 取不在集合S中距离最短的点
heap.pop();
int ver = k.second;
int distance = k.first;
if (st[ver]) continue;
st[ver] = 1 ; //把该点加入集合S
for (int i = h[ver]; i !=0; i = adj[i].next)//在点中依次找相连点
{
int j = adj[i].v; //取出和ver相连的点
if (dist[j] > distance + adj[i].w)
{
dist[j] = distance+adj[i].w;
heap.push({dist[j],j});
}
}
}
}
int main(){
scanf("%lld%lld%lld",&n,&m,&s);//writing code
while(m--){
long long x,y,c;
scanf("%lld%lld%lld",&x,&y,&c);
insert(x,y,c);
}
dijkstra();
for(int i=1;i<=n;i++)printf("%lld ",dist[i]);//依次输出该点与其他点的最小相连路径
}
Bellman_Ford
限制 \(k\) 次松弛,意味着找到不超过 \(k\) 条边组成的最短路径,每次松弛遍历所有边 \(u\to v\) 并用 \(dis_u\) 更新 \(dis_v\),需要注意这里可以利用一个滚动数组维护,需要保留上一次信息,时间复杂度 \(O(km)\)。
优点:可以处理负环图,也可以判负环,方法与 SPFA 相同。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5,INF=1e9;
int n,m,idx,head[N];
struct Edge{int u,v,w;}e[N<<1];
int dis[N][2];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v>>w;
e[i]=(Edge){v,u,w};
}
for(int i=1;i<=n+1;i++)
dis[i][0]=dis[i][1]=INF;
for(int i=1;i<=n;i++)
e[++m]=(Edge){n+1,i,0};
dis[n+1][0]=0;
for(int K=1;K<=n;K++){
for(int i=1;i<=n+1;i++)
dis[i][K&1]=dis[i][!(K&1)];
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
dis[v][K&1]=min(dis[v][K&1],dis[u][!(K&1)]+w);
}
}
bool tf=1;
for(int i=1;i<=m;i++)
if(dis[e[i].u][n&1]+e[i].w<dis[e[i].v][n&1]){tf=0;break;}
if(!tf)cout<<"NO";
else for(int i=1;i<=n;i++)cout<<dis[i][n&1]<<' ';
return 0;
}
SPFA
SPFA 已死?复杂度被证明是 \(O(nm)\) 级的,但是因为队列优化实际跑起来没那么慢,除非出题人恶意卡评测。而自从有人发现这个东西可以卡,就没有人让它再通过任何一道新图论最短路题(?)。实际上是先入队起点 \(u\),然后像 Dijkstra 一样向相邻节点松弛边,如果更新成功且新点 \(v\) 不在队列中那么就入队。
优点:可以判断负环。
-
给每个节点加一个最短路长度 \(cnt_u\),然后如果出现 \(cnt_u\geq n\) 说明至少有一个节点被复用了,则存在负环。
-
遍历每条边,判断是否存在 \(u\to v\) 权值为 \(w\) 的边使得 \(dis_u+w<dis_v\),若存在则有负环。
Floyd
\(O(n^3)\),每次枚举一个中间点 \(k\),用 \(dis_{i,k}+dis_{k,j}\) 更新 \(dis_{i,j}\),然后有直接连边提前赋值,没有赋为 \(+\infty\) 即可。
优点:全源汇最短路。
点击查看代码(无向图版)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=105;
int n,m;
int dis[N][N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)dis[i][j]=1e9;
while(m--){
int u,v,w;
cin>>u>>v>>w;
dis[u][v]=min(dis[u][v],w);
dis[v][u]=min(dis[v][u],w);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
cout<<dis[i][j]<<' ';
cout<<'\n';
}
return 0;
}

浙公网安备 33010602011771号