(笔记)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\) 不在队列中那么就入队。

优点:可以判断负环。

  1. 给每个节点加一个最短路长度 \(cnt_u\),然后如果出现 \(cnt_u\geq n\) 说明至少有一个节点被复用了,则存在负环。

  2. 遍历每条边,判断是否存在 \(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;
}

posted @ 2025-04-24 14:55  TBSF_0207  阅读(36)  评论(0)    收藏  举报