最短路算法桶(Dijkstra,Floyd,Bellman-Ford,Spfa)

自己对最短路的简单总结并不涉及详解

 

1.Dijkstra

算法思想:(求单源最短路,朴素算法复杂度O(n^2),堆优化O(nlogn)

将点分为两类,一类为已被更新最短距离的点为“标记点”,另一类为还没有被更新最短距离的点为“未标记点”。初始dist[i]=INF

一开始将起点到起点的距离标记为0,加入优先队列(dist从小到大排序);每次取dist最小的点(即与起始点距离最小的点)设为“标记点”,然后枚举和该点相连的点,更新他们的最短路dist[i],更新的原则就是以该点为中转点,更新方程为dist[i]=min(dist[j]+nw,dist[i])  [ j为中转点,i为更新点,w为j到i的权值 ]  然后将与该点相关联的点都加入队列,循环往复,直到队列为空。最后全部的点都访问完毕时,dist都被更新,即起点到到该点的最短路。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=LONG_LONG_MAX;
const ll inf=LONG_LONG_MIN;
const ll maxn=3e5+5;
ll n,m,ss,ee;
struct node {
	ll p; //当前点 
	ll dis; //起始点到该点的最短距离 
	node(ll np=0,ll ndis=0) {
		p=np,dis=ndis;
	}
	bool operator <(const node& x)const {
		return dis>x.dis;
	}
};
struct edge {
	ll id; 
	ll to;
	ll w;
	edge(ll nid=0,ll nto=0,ll nw=0) {
		id=nid,to=nto,w=nw;
	}
};
vector<edge> e[maxn];
bool vis[maxn];
ll dist[maxn];
void Dijkstra(int start) {
	memset(dist, 0x3f, sizeof(dist));
	memset(vis, false, sizeof(vis));
	dist[start]=0;
	priority_queue<node> q;
	q.push(node(start,0));
	
	while(!q.empty()) {
		node tmp=q.top();
		q.pop();
		int np=tmp.p;
		if(vis[np])
			continue;
		else {
			vis[np]=1;
			for(int i=0; i<e[np].size(); ++i) {
				edge ne=e[np][i];
				ll nto=ne.to;
				ll nw=ne.w;
				ll nid=ne.id;
				if(dist[nto]>dist[np]+nw) {
					dist[nto]=dist[np]+nw;
					q.push(node(nto,dist[nto])); 
				}
			}
		}
	}
}
int main() {
	scanf("%lld%lld%lld%lld",&n,&m,&ss,&ee);
	ll x,y,w;
	for(int i=1; i<=m; ++i) {
		scanf("%lld%lld%lld",&x,&y,&w);//无向图建图 
		e[x].push_back(edge(i,y,w));
		e[y].push_back(edge(i,x,w));
	}
	Dijkstra(ss);
	cout<<dist[ee]<<endl;
	return 0;
}
 

例题:https://www.luogu.org/problemnew/show/P2384

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=LONG_LONG_MAX;
const ll inf=LONG_LONG_MIN;
const ll maxn=3e5+5;
const ll mod=9987;
ll n,m,k;
struct node {
	ll p; //当前点 
	ll dis; //起始点到该点的最短距离 
	node(ll np=0,ll ndis=0) {
		p=np,dis=ndis;
	}
	bool operator <(const node& x)const {
		return dis>x.dis;
	}
};
struct edge {
	ll id; 
	ll to;
	ll w;
	edge(ll nid=0,ll nto=0,ll nw=0) {
		id=nid,to=nto,w=nw;
	}
};
vector<edge> e[maxn];
bool vis[maxn];
ll dist[maxn];
void Dijkstra() {
	memset(dist, 0x3f, sizeof(dist));
	memset(vis, false, sizeof(vis));
	dist[1]=0;
	priority_queue<node> q;
	q.push(node(1,0));
	while(!q.empty()) {
		node tmp=q.top();
		q.pop();
		int np=tmp.p;
		if(vis[np])
			continue;
		else {
			vis[np]=1;
			for(int i=0; i<(int)e[np].size(); ++i) {
				edge ne=e[np][i];
				ll nto=ne.to;
				ll nw=ne.w;
				ll nid=ne.id;
				if(dist[np]==0){
					dist[nto]=(dist[np]+nw)%mod;
					q.push(node(nto,dist[nto]));
				}
				else if(dist[nto]>dist[np]*nw) {
					dist[nto]=(dist[np]*nw)%mod;
					q.push(node(nto,dist[nto])); 
				}
			}
		}
	}
}
int main() {
	scanf("%lld%lld",&n,&m);
	ll x,y,w;
	for(int i=1; i<=m; ++i) {
		scanf("%lld%lld%lld",&x,&y,&w);//有向图建图 
		e[x].push_back(edge(i,y,w));
	}
	Dijkstra();
	cout<<dist[n]%mod<<endl; 
	return 0;
}

2.Floyd

算法思想:(求多源最短路,算法复杂度O(n^3)

3层循环,第一层枚举中间点k(以k为中转点),第二层与第三层枚举两个端点i,j。若有dis[i][j] > dis[i][k] + dis[k][j] 则把dis[i][j]更新成dis[i][k] + dis[k][j]。不断去更新dis[i][j],就是一种dp的思想,因为算法复杂度高,一般只有数据量小的时候才用得上。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=INT_MAX;
const ll inf=INT_MIN;
const ll maxn=1e3+5;
ll n,m;
ll e[maxn][maxn];
int main() {
	scanf("%lld%lld",&n,&m);
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=n; ++j) {
			if(i==j)	e[i][j]=0;
			else	e[i][j]=INF;
		}
	}
	ll x,y,w;
	for(int i=1; i<=m; ++i) {
		scanf("%lld%lld%lld",&x,&y,&w);//有向图建图
		e[x][y]=w;
	}

	for(int k=1; k<=n; k++) { //经过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];
		}
	}
	
	for(int i=1; i<=n; i++) {//从i到j的最短距离 
		for(int j=1; j<=n; j++) {
			printf("%lld",e[i][j]);
		}
		printf("\n");
	}
	return 0;
}

例题:https://www.luogu.org/problemnew/show/P2888

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<cstring>
#include<algorithm>
#include<limits.h>
using namespace std;
typedef long long ll;
const ll INF=INT_MAX;
const ll inf=INT_MIN;
const ll maxn=1e3+5;
ll n,m,k;
ll e[maxn][maxn];
int main() {
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=n; ++j) {
			if(i==j)	e[i][j]=0;
			else	e[i][j]=INF;
		}
	}
	ll x,y,w;
	for(int i=1; i<=m; ++i) {
		scanf("%lld%lld%lld",&x,&y,&w);
		e[x][y]=w;
	}

	for(int k=1; k<=n; k++) {
		for(int i=1; i<=n; i++) {
			for(int j=1; j<=n; j++) {
				e[i][j]=min(max(e[i][k],e[k][j]),e[i][j]);
			}
		}
	}
	for(int i=1; i<=k; i++) {
		scanf("%lld%lld",&x,&y);
		if(e[x][y]==INF)	cout<<-1<<endl;
		else	cout<<e[x][y]<<endl;
	}
	return 0;
}

3.Bellman-Ford

算法思想:Bellman-Ford也是来算单源最短路的,但是复杂度是O(v*e),是高于Dijkstra的,但是它可以用来检测负权回路,和带负值的最短路。

就是经过v-1次松弛(初始点固定了,最差的情况也是每一次松弛只松弛一个点,有v-1个),每次去尝试所有的边能否去松弛各个点。松弛过后,如果再进行一次松弛还能被松弛的话,则说明存在负权回路。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
int dist[maxn];
int v,e;
//顶点和边
struct edge {
	int s;
	int e;
	int w;
};
vector<edge> tt;
bool bellmanFord(int src) {
	memset(dist,0x3f,sizeof(dist));
	dist[src]=0;

	for(int i=1; i<v; ++i) {
		for(int j=0; j<e; ++j) {
			int s=tt[j].s;
			int e=tt[j].e;
			int w=tt[j].w;
			if(dist[s]!=INF && dist[e]>dist[s]+w) {
				dist[e]=dist[s]+w;
			}
		}
	}
	
	//检测负权回路
	bool isBack=false;
	for(int j=0; j<e; ++j) {
		int s=tt[j].s;
		int e=tt[j].e;
		int w=tt[j].w;
		if(dist[s]!=INF && dist[e]>dist[s]+w) {
			isBack=true;
			break;
		}
	}
	return isBack;
}

int main() {
	cin>>v>>e;
	int ns,ne,nw;
	for(int i=0; i<e; ++i) {
		cin>>ns>>ne>>nw;
		tt.push_back(edge {ns,ne,nw});
	}
	bool res=bellmanFord(0);
	if(res) {
		cout<<"有负权回路"<<endl;
	} else {
		cout<<"无负权回路"<<endl;
		for(int i=1; i<=v; ++i) {
			cout<<dist[i]<<' ';
		}
		cout<<endl;
	}
	return 0;
}

4.Spfa

算法思想:SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。

用一个先进先出的队列来维护,每次取出队首结点p,然后用该点去松弛和它相连的点to,如果到dist[to]被减小,并且此时队列中没有这个点,那就将其加入队列(因为,队列只是维护的点,假设t已经在队列中,相同的点t存储的dist信息是一样的,这里dist[t]被减小,再将t点加入队列毫无意义),直到队列为空。

*判断负权环:如果一个点被加入队列次数超过顶点数V,则存在负权环。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
int dist[maxn];//距离
int pushnum[maxn];//入队次数
bool vis[maxn];//是否入队中
struct edge {
	int to;
	int w;
};
vector<edge> tt[maxn];
int N,M,S;
//点数,边数,起点
bool spfa(int start) {
	memset(dist,0x3f,sizeof(dist));
	memset(vis,false,sizeof(vis));
	memset(pushnum,0,sizeof(pushnum));
	dist[start]=0;
	vis[start]=true;
	pushnum[start]++;

	queue<int> q;
	q.push(start);
	while(!q.empty()) {
		int p=q.front();
		q.pop();
		vis[p]=false;
		for(int i=0; i<(int)tt[p].size(); ++i) {
			edge tmp=tt[p][i];
			if(dist[tmp.to]>dist[p]+tmp.w) {
				dist[tmp.to]=dist[p]+tmp.w;
				if(vis[tmp.to]==false) {
					q.push(tmp.to);
					vis[tmp.to]=true;
					pushnum[tmp.to]++;
					if(pushnum[tmp.to]>N) {
						return false;
					}
				}

			}
		}
	}
	return true;
}
int main() {
	cin>>N>>M>>S;
	int x,y,z;
	for(int i=0; i<M; ++i) {//有向图建图
		cin>>x>>y>>z;
		tt[x].push_back(edge {y,z});
	}
	if(spfa(S)) {
		for(int i=1; i<=N; ++i) {
			cout<<dist[i]<<' ';
		}
		cout<<endl;
	}
	else{
		cout<<"有负环"<<endl; 
	}
	return 0;
}

 

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/tdd_master/article/details/84036576
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞