图论最短路之Floyd、Dijkstra、Bellman-Ford、Spfa(详细分析)

本文详细介绍了四种最短路径算法:Floyd、Dijkstra、Bellman-Ford和SPFA,包括它们的算法描述、代码实现以及适用情况。Floyd适合负边权,Dijkstra适用于正权图,Bellman-Ford能处理负权图,而SPFA是对Bellman-Ford的优化,适用于稀疏图。
摘要由CSDN通过智能技术生成

这一篇博客主要是最短路四大方法模板的归纳

一、Floyd

佛洛伊德是最简单的最短路径算法,可以计算图中任意两点间的最短路径。时间复杂度为O(N3),适用于出现负边权的情况。
算法描述:
( a )初始化:点u、v如果有边相连,则dis[u][v]=w[u][v]
如果不相连,则dis[u][v]=0x3f
( b )模板:

for(k=1;k<=n;k++){
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			if(dis[i][j]>dis[i][k]+dis[k][j])
			  	dis[i][j]=dis[i][k]+dis[k][j];
		}
	}
}

( c )算法结束:dis[i][j]得出的就是任意起点i到任意终点j的最短路径。

完整代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=1005;
int dis[M][M];
int main(){
	int n,m;
	scanf("%d %d",&n,&m);//n代表点数,m代表边数
	for(int i=1;i<=m;i++){
		int s,t,w;
		scanf("%d %d %d",&s,&t,&w);
		dis[s][t]=w;
	//  dis[t][s]=w;如是无向图则加上这一行
	}
	int S,T;
	scanf("%d %d",&S,&T);
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(dis[i][j]>dis[i][k]+dis[k][j])
			  		dis[i][j]=dis[i][k]+dis[k][j];
			}
		}
	}
	printf("%d",dis[S][T]);
}


二、Dijkstra

结点分成两组:已经确定最短路、尚未确定最短路不断从第2组中选择路径长度最短的点放入第1组并扩展本质是贪心,只能应用于正权图

算法描述:
设起点为s,dis[v]表示从指定起点s到v的最短路径,pre[v]为v的前驱,用来输出路径
(a)初始化

	memset(dis,+)memset(vis,0)(v:1~n)dis[v]=w[s][v],bool vis[v]=0;
    dis[s]=0;pre[s]=0;vis[s]=1

(b)

for(i=1;i<=n-1;i++)
        1 在没有被访问过的点中找一个相邻顶点k,使得dis[k]是最小的;
        2 k标记为已确定的最短路vis[k]=true;
        3for循环更新与k相连的每个未确定最短路径的顶点v (所有未确定最短路的点都松弛更新)
      	if(dis[k]+w[k][v]<dis[v]) dis[v]=dis[k]+w[k][v],pre[v]=k;   

(c)算法结束dis[v]为s到v的最短路距离;pre[v]为v的前驱结点,用来输出路径。



Dijkstra 分为三种类型:

Dijkstra 分为三种类型
普通
邻接表
堆优化

① 普通代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M=2005;
int G[M][M];
int dis[M];
bool flag[M];
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			G[i][j]=0x3f3f3f3f;
		}
		dis[i]=0x3f3f3f3f;
		G[i][i]=0;
	}//赋初值,最好不要使用memset
	for(int i=1;i<=m;i++){
		int s,t,w;
		scanf("%d %d %d",&s,&t,&w);
		G[s][t]=min(G[s][t],w);
	  //G[t][s]=min(G[t][s],w);如果是双向加
	}
	int S,T;
	scanf("%d %d",&S,&T);
	dis[S]=0,flag[S]=true;
	for(int i=1;i<=n;i++){
		int id=0;
		for(int j=1;j<=n;j++){
			if((!flag[j])&&(id==0||dis[id]>dis[j])){
				id=j;
			}
		}
		flag[id]=true;
		for(int j=1;j<=n;j++){
			dis[j]=min(dis[j],dis[id]+G[id][j]);
		}
	}
	printf("%d",dis[T]);
} 

② 邻接表代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int M=1005;
int n,m;
int dis[M];
bool flag[M];
struct edge{
	int v,w;
	edge(){}
	edge(int V,int W){
		v=V,w=W;
	}
};
vector<edge> G[M];
int Dijkstra(int S,int T){
	memset(dis,0x3f,sizeof(dis)); 
	dis[S]=0;
	for(int i=1;i<=n;i++){
		int id=0;
		for(int j=1;j<=n;j++){
			if(dis[j]<dis[id]&&flag[j]==false){
				id=j;
			}
		}
		flag[id]=true;
		int siz=G[id].size();
		for(int j=0;j<siz;j++){
			int v=G[id][j].v,w=G[id][j].w;
			dis[v]=min(dis[v],dis[id]+w);
		}
	}
	return dis[T];
}
void Edge(int s,int t,int w){
	G[s].push_back(edge(t,w));
//	G[t].push_back(edge(s,w));如是无向加
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int s,t,w;
		scanf("%d %d %d",&s,&t,&w);
		Edge(s,t,w);
	}
	int S,T;
	scanf("%d %d",&S,&T);
	printf("%d",Dijkstra(S,T));
}

③ 堆优化代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue> 
using namespace std; 
const int M=100005;
int dis[M];
bool flag[M];
struct edge{
	int v,w;
	edge(){
	}
	edge(int V,int W){
		v=V,w=W;
	}
};
struct node{
	int u,dis;
	node(){
	}
	node(int U,int D){
		u=U,dis=D;
	}
	friend bool operator < (node a,node b){return a.dis>b.dis;}
};
vector<edge> G[M];
priority_queue<node> Q;
int Dijkstra(int S,int T){
	memset(dis,0x3f,sizeof(dis));
	dis[S]=0,Q.push(node(S,0));
	while(!Q.empty()){
		int u=Q.top().u;
		Q.pop();
		if(flag[u]) continue;
		flag[u]=true;
		int v,w,siz=G[u].size();
		for(int i=0;i<siz;i++){
			v=G[u][i].v,w=G[u][i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				Q.push(node(v,dis[v]));
			}
		}
	}
	return dis[T];
}
void Edge(int x,int y,int z){
	G[x].push_back(edge(y,z));
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int s,t,w;
		scanf("%d %d %d",&s,&t,&w);
		Edge(s,t,w);
	}
	int S,T;
	scanf("%d %d",&S,&T);
	printf("%d",Dijkstra(S,T));
}


三、Bellman-Ford

对每条边执行更新,迭代N-1次,可以应用于负权图。
(1)初始化:dis[s] = 0, dis[i] = 0x3f3f3f3f(i≠s), pre[s]=0。
(2)

for (i = 1; i <= nodenum – 1; i ++)
       for (j = 1; j <= edgenum – 1; j ++)
               if (dis[u] + w[j] < dis[v]) {         //relax
                       dis[v] = dis[u] + w[j];
                       pre[v] = u;
               }

完整代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int M=100005;
int dis[M];
int S,T,n,m;
struct node{
	int ui,vi,wi;
}edge[M];
bool BellmanFord(){
	memset(dis,0x3f,sizeof(dis));
	for(int i=1;i<=n-1;i++){
		for(int j=1;j<=m;j++){
			if(dis[edge[j].vi]>dis[edge[j].ui]+edge[j].wi){
				dis[edge[j].vi]=dis[edge[j].ui]+edge[j].wi;
			}
		}
	}
	bool flag=true;
	for(int i=1;i<=m;i++){
		if(dis[edge[i].vi]>dis[edge[i].ui]+edge[i].wi){
			flag=false;
			break;
		}
	}
	if(flag==true){
		return true;
	}
	return false;
}
int main(){
	
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&edge[i].ui,&edge[i].vi,&edge[i].wi);
	}
	int S,T;
	scanf("%d %d",&S,&T);
	for(int i=1;i<=m;i++){
		if(edge[i].ui==S){
			dis[edge[i].vi]=edge[i].wi;
		}
	} 
	dis[S]=0;
	if(BellmanFord()){
		printf("%d",dis[T]);
		return 0;
	}
	return 0;
}


四、Spfa

SPFA = 队列优化的Bellman-Ford算法,本质上还是迭代——每更新一次就考虑入队稀疏图上O(kN),稠密图上退化到O(N^2),可以应用于负权图

算法实现:
在Bellmanford算法中,有许多松弛是无效的。这给了我们很大的改进的空间。SPFA算法正是对Bellmanford算法的改进。它是由西南交通大学段丁凡1994提出的。它采用了队列和松弛技术。先将源点加入队列。然后从队列中取出一个点(此时该点为源点),对该点的邻接点进行松弛,如果该邻接点松弛成功且不在队列中,则把该点加入队列。如此循环往复,直到队列为空,则求出了最短路径。
判断有无负环:如果某个点进入队列的次数超过N次则存在负环 ( 存在负环则无最短路径,如果有负环则会无限松弛,而一个带n个点的图至多松弛n-1次)

完整代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int M=100005;
int dis[M];
bool flag[M];
int S,T,n,m;
struct node{
	int si,ti,wi;
	node(){}
	node(int X,int Y){
		ti=X,wi=Y;
	}
};
queue<int> q;
vector<node> vec[M];
void Edge(int x,int y,int z){
	vec[x].push_back(node(y,z));
//	vec[y].push_back(node(x,z));如是无向图加
}
int SPFA(){
	memset(dis,0x3f,sizeof(dis));
	memset(flag,false,sizeof(flag));
	dis[S]=0,flag[S]=true,q.push(S);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		flag[x]=false;
		int siz=vec[x].size();
		for(int i=0;i<siz;i++){
			int Ti=vec[x][i].ti,Wi=vec[x][i].wi;
			if(dis[Ti]>dis[x]+Wi){
				dis[Ti]=dis[x]+Wi;
				if(!flag[x]){
					q.push(Ti),flag[Ti]=true;
				}
			}
		}
	}
	return dis[T];
}
int main(){
	memset(dis,0x3f,sizeof(dis));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int s,t,w;
		scanf("%d %d %d",&s,&t,&w);
		Edge(s,t,w);
	}
	int S,T;
	scanf("%d %d",&S,&T);
	int id1=SPFA();
	printf("%d",id1);
}

总结

这次csp做的非常不好,其主要原因是在于思维没有打开,还有在考场上的策略有错误,而导致时间的不够以及题的思考深度不够。
但是这次也深刻的提醒与我,在平时的训练中更需要自己的思维训练。

所有代码纯手打,点个赞再走呗!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值