最短路各算法总结

个人建议:先学存图再看算法
先看:


Floyd算法

适用情况:

求任意两点间的最短路,不能处理负权回路(负权环),可处理负权边。

时间复杂度:

O(N3)

算法思想:

如果要两点a,b间的路程变短,就需要引入新的顶点k1,k2,……kn,使a—>k1—>k2—>……—>kn—>b更短。 即对a和b之间所有的其他点进行松弛。
DP:状态状态转移方程:d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])
利用滚动数组优化:a[i][j]=min(a[i][j],a[i][k]+a[k][j])【动态规划:0/1背包问题】
相关文章:https://blog.csdn.net/qq_40507857/article/details/80657007

代码:

#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int a[100][100];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int i,j,k,n,m,t1,t2,t3;
	cin>>n>>m;
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			if(i==j)
				a[i][j]=0;
			else
				a[i][j]=maxn;
	for(i=1;i<=m;i++)
	{
		cin>>t1>>t2>>t3;
		a[t1][t2]=t3;
	}
	//Floyd-Warshall算法核心
	for(k=1;k<=n;k++)
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
				if(a[i][j]>a[i][k]+a[k][j])
					a[i][j]=a[i][k]+a[k][j];
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
			cout<<a[i][j]<<" ";
		cout<<endl;
	 } 
	return 0;
}

Dijkstra算法

适用情况:

求从一个顶点到其余各顶点的最短路径算法,不能处理负权边。

时间复杂度:

O(N2)
利用堆优化;在边数M少于N^2的稀疏图来说,可以用邻接表代替邻接矩阵。可优化为O((M+N)logN)。

算法思想:

类似BFS+贪心。通过边来松弛1号顶点到其余各点的距离。选择离起点最近的一个点,扩散出去。
选择距离1号顶点最近的一个顶点作为确定的长度(因为边权值非负,所以距离1号顶点距离最短即意味着不可能通过其他的点中转来缩短距离。然后讨论从已选的点i到其他点j的距离是否能够更短,即dis[j]=min(dis[j],dis[i]+a[i][j])。
如果book数组的值全为1,则1号点到所有的点的距离确定,即1号点到所有点的最短距离确定。

代码:

#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int a[100][100],dis[100],book[100];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int i,j,n,m,t1,t2,t3,u,v,min1;
	cin>>n>>m;
	//初始化
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			if(i==j)
				a[i][j]=0;
			else
				a[i][j]=maxn;
	for(i=1;i<=m;i++)
	{
		cin>>t1>>t2>>t3;
		a[t1][t2]=t3;	
	} 
	//初始化dis数组,表示1号顶点到其余各点的距离
	for(i=1;i<=n;i++)
		dis[i]=a[1][i];
	//初始化book数组,book数组表示从1号顶点到其他顶点的距离是否确定,确定为1,不确定为0
	for(i=1;i<=n;i++)
		book[i]=0;
	book[1]=1;
	//dijkstra算法核心语句
	for(i=1;i<=n;i++)
	{
		min1=maxn;
		for(j=1;j<=n;j++)
			if(book[j]==0 && dis[j]<min1)
			{
				min1=dis[j];
				u=j;
			}//找出离1号顶点最近的顶点 
		book[u]=1;//这个点到1号顶点的距离即是最短距离,将它标记为确定的距离。
				  //因为所有的边权值都非负。 
				  //所以对于距离1号顶点距离最短的一个点,无法通过其他点的中转来获得更短的距离。 
		for(v=1;v<=n;v++)
			if(a[u][v]<maxn)
				dis[v]=min(dis[v],dis[u]+a[u][v]);
		//在u->v的距离不为无穷大时,即从u到v有路时,判断是否能够通过中转来缩短距离。 
	} 
	for(i=1;i<=n;i++)
		cout<<dis[i]<<" "; 
	return 0;
}

优化:

堆优化:

优化思想:

用堆每次弹出最小值来代替每轮进行的最短边查找。堆用小根堆,可以保证每次在堆顶的元素是最小的。可以使时间复杂度降为O(M+NlogN)。

代码:
//注:手打的代码模板,不保证正确性QAQ 
#include<bits/stdc++.h>
const int inf=99999999;
using namespace std;
int vis[1000],maze[1000][1000],dis[1000];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,i,j;
	int a,b,c;
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que; 
	//优先队列,用pair第一个值存权值,第二个值存点下标,greater是从按权值小到大排序,即小根堆。 
	cin>>n>>m;
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			if(i==j) maze[i][j]=0;
			else maze[i][j]=inf;
	for(i=1;i<=m;i++)
	{
		cin>>a>>b>>c;
		maze[a][b]=c;	
	}
	memset(vis,0,sizeof(vis)); 
	for(i=1;i<=n;i++)
		dis[i]=maze[1][i];//初始化dis数组 
	que.push(make_pair(0,1));//将第一个点放入优先队列 
	while(!que.empty())
	{
		int k;
		int pre=que.top().second;//pre表示pair的第二个值,即对这个点的边进行松弛操作 
		que.pop();
		if(vis[pre])
			continue;
		vis[pre]=1;
		for(k=1;k<=n;k++)
			if(maze[pre][k]<inf)
				if(dis[k]>=dis[pre]+maze[pre][k])//如果可以进行松弛操作,就将可以改变的点放入优先队列 
				{
					dis[k]=dis[pre]+maze[pre][k];
					que.push(make_pair(dis[k],k));
				}
	}
	for(i=1;i<=n;i++)
		cout<<dis[i]<<" ";
	return 0;
}

堆优化+链式前向星:

//注:手打的代码模板,不保证正确性QAQ 
#include<bits/stdc++.h>
#define pii pair<int,int>
const int N=1e5+10;
const int inf=99999999;
using namespace std;
struct edge
{
	int next,to,w;
}maze[N];
int head[N]={0},len=0,vis[N]={0},dis[N];
void add(int u,int v,int w)
{
	maze[++len]={head[u],v,w};
	head[u]=len;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,i;
	cin>>n>>m;
	for(i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	for(i=1;i<=n;i++)
		dis[i]=inf;
	dis[1]=0;
	priority_queue<pii,vector<pii>,greater<pii> >que;
	que.push(make_pair(0,1));
	while(!que.empty())
	{
		int pre=que.top().second;
		que.pop();
		if(vis[pre])
			continue;
		vis[pre]=1;
		for(i=head[pre];i;i=maze[i].next)
		{
			int v=maze[i].to,w=maze[i].w;
			if(dis[v]>dis[pre]+w)
			{
				dis[v]=dis[pre]+w;
				que.push(make_pair(dis[v],v));
			}
		}
	}
	for(i=1;i<=n;i++)
		cout<<dis[i]<<" "; 
	return 0;
}

Ford算法

适用情况:

可以解决带负权边的图,可以判断是否存在负权回路(负权环)。

时间复杂度:

O(NM)
优化:可以用check来判断dis数组在本轮循环中是否发生变化。如果dis数组没有发生变化,即所求已经是最短路径,即可跳出循环。

算法思想:

对所有的边进行n-1次松弛操作。
进行k次松弛后,我们可以得到1号点最多经过k条边到其余点的最短距离。
能否通过u[i]->vi的边使1号顶点到v[i]的距离缩短,即:dis[v[i]]=min(dis[v[i]],dis[u[i]]+w[i])。
对于正权回路,去掉的话,我们肯定可以得到更短的距离。
对于负权回路,每循环一次都会得到更短的距离。所以我们可以通过进行n-1次循环之后,能不能继续松弛来判断图中是否存在负权回路。

代码:

#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int dis[100],u[100],v[100],w[100],bak[100];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int i,k,n,m,check,flag;
	cin>>n>>m;
	//读入数据,即从边u[i]->v[i]的距离w[i] 
	for(i=1;i<=m;i++)
		cin>>u[i]>>v[i]>>w[i];
	//初始化dis数组
	for(i=1;i<=n;i++)
		dis[i]=maxn;
	dis[1]=0; 
	//Ford算法核心语句
	for(k=1;k<=n-1;k++)
	{
		check=0;//记录本轮dis数组是否会更新
		//进行松弛
		for(i=1;i<=m;i++)
			if(dis[v[i]]>dis[u[i]]+w[i]) //对于1号点到v[i]号点,是否存在u[i]点中转使距离更短 
			{
				dis[v[i]]=dis[u[i]]+w[i];
				check=1;
			}
		if(!check)
			break; 
	} 
	//检查负权回路
	flag=0;
	for(i=1;i<=m;i++)
		if(dis[v[i]]>dis[u[i]]+w[i]) //如果再进行n-1次松弛后,仍然能松弛,即含有负权环。 
			flag=1;
	if(flag)
		cout<<"此图含有负权回路"<<endl; 
	else
		for(i=1;i<=n;i++)
			cout<<dis[i]<<" "; 
	return 0;
}

SPFA算法(Ford算法的队列优化)

适用情况:

同Ford算法。

时间复杂度:

最坏情况O(NM)

算法思想:

用邻接表存图;队列操作。
取出队首,对其出边进行松弛操作,将最短路径能够发生改变的点放入队尾。用数组book标识当前那些点已在队列中。
形式上有些类似于BFS。
对于负环的判定:如果一个点进入队列的次数大于等于n-1次,那么就存在负权环。

代码:

#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,i,j,k;
	int u[8],v[8],w[8];//储存边u[i]->v[i]的权值w[i] 
	int first[6],next[8];
	int dis[6]={0},book[6]={0};
	int que[101]={0},head=1,tail=1;//定义队列
	cin>>n>>m;
	for(i=1;i<=n;i++)
		dis[i]=maxn;
	dis[1]=0;
	for(i=1;i<=n;i++)
		first[i]=-1;//初始化first数组,表示1~n顶点暂时没边
	for(i=1;i<=m;i++)
	{
		cin>>u[i]>>v[i]>>w[i];
		//建立邻接表 
		next[i]=first[u[i]];
		first[u[i]]=i; 
	} 
	que[tail++]=1;
	book[1]=1;//1号顶点入队
	while(head<tail)
	{
		k=first[que[head]];//当前要处理的队首顶点
		while(k!=-1)//扫描当前顶点所有边 
		{
			if(dis[v[k]]>dis[u[k]]+w[k])
			{
				dis[v[k]]=dis[u[k]]+w[k];
				if(book[v[k]]==0)
				{
					que[tail++]=v[k];
					book[v[k]]=1;
				}
			} 
			k=next[k];
		} 
		book[que[head]]=0;
		head++;//出队 
	} 
	for(i=1;i<=n;i++)
		cout<<dis[i]<<" ";
	return 0;
}

用链式前向星的代码:

//手打模板,不保证正确性,QAQ.
#include<bits/stdc++.h>
const int inf=99999999;
using namespace std;
struct edge
{
	int next,to,w;
}maze[1000];
int head[100],dis[100],vis[100],num[100];
int len=0,n,m;
void add(int u,int v,int w)
{
	maze[++len]={head[u],v,w};
	head[u]=len;
}
int spfa()
{
	queue<int> que;
	que.push(1);
	while(!que.empty())
	{
		int pre=que.front(),i;
		que.pop();
		vis[pre]=0;
		for(i=head[pre];i;i=maze[i].next)
		{
			int v=maze[pre].to;
			if(dis[v]>dis[pre]+maze[i].w)
			{
				dis[v]=dis[pre]+maze[i].w;
				if(!vis[v])
				{
					num[v]++;
					if(num[v]>=n)
						return 1;//如果入队次数大于n表示有负环。 
					vis[v]=1;
					que.push(v);
				}
			}
		}
	}
	return 0;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	memset(vis,0,sizeof(vis));
	memset(num,0,sizeof(num));//记录一个顶点的入队次数。 
	memset(head,0,sizeof(head));
	int i;
	cin>>n>>m;
	for(i=1;i<=n;i++)
		dis[i]=inf;
	for(i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
//		add(b,a,c);//无向图 
	}
	dis[1]=0;
	vis[1]=1;
	num[1]=1;
	if(spfa())
		cout<<"含负环"<<endl;
	else 
		for(i=1;i<=n;i++)
			cout<<dis[i]<<" "; 
	return 0;
}

相关题目题解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值