0
点赞
收藏
分享

微信扫一扫

最短路问题

水墨_青花 2022-02-25 阅读 85

注意与最小生成树的区分:①边的权重全都相同,可以用prim和Kruskal算法。
                                           ②权值不相同,就要用以下的算法。

一、单源最短路
1、所有边都是正数

①朴素的Dijkstra算法(适用于稠密图,用邻接矩阵存图)O(n^2)
思想:每次将离起点最近的点加入起点所在的集合S,然后更新每个点到集合S的最小距离。
步骤:①初始化距离memset(d,INF,sizeof(d)),d[1]=0;②循环遍历不在集合S中的且离S最近的距离。

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int N=510,INF=0x3f3f3f3f;
int g[N][N],d[N];
bool st[N];
int n,m;
int dijkstra()
{
	memset(d,INF,sizeof(d));
	d[1]=0;
	for(int i=1;i<=n;i++)
	{
		int t=-1;
		for(int j=1;j<=n;j++)//找出到1这个集合的最小距离
			if(!st[j]&&(t==-1||d[t]>d[j]))
				t=j;
		st[t]=true;
		for(int j=1;j<=n;j++)//更新每个点到集合的最小距离
			d[j]=min(d[j],d[t]+g[t][j]);
	}
	if(d[n]==INF) return -1;
	return d[n];
}
int main()
{
	cin>>n>>m;
	memset(g,INF,sizeof(g));
	int a,b,c;
	for(int i=0;i<m;i++)
	{
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c);//针对重边和自环
	}
	int t=dijkstra();
	cout<<t<<endl;
	return 0;
}

②堆优化的Dijkstra算法(对于稀疏图,用邻接表存图)O(mlogn)
思路:用优先队列存储每条边的权值,每次取肯定取出的是所有边,在更新每个点到起点的最小值的时候,是更新当前t的所有出边。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=1.5e5+10,INF=0x3f3f3f3f;
int e[N],w[N],ne[N],h[N],idx;
int d[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
	memset(d,INF,sizeof(d));
	d[1]=0;
	priority_queue<PII,vector<PII>,greater<PII>> heap;//小根堆
	heap.push({0,1});
	while(heap.size())
	{
		auto t=heap.top();
		heap.pop();
		int dis=t.first,ver=t.second;
		if(st[ver]) continue;
		st[ver]=true;
		for(int i=h[ver];i!=-1;i=ne[i])//遍历从t出去的所有边
		{
			int j=e[i];
			if(d[j]>dis+w[i])
			{
				d[j]=dis+w[i];
				heap.push({d[j],j});
			}
		}
	}
	if(d[n]==INF) return -1;
	return d[n];
}
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	int x,y,z;
	for(int i=0;i<m;i++)
	{
		cin>>x>>y>>z;
		add(x,y,z);
	}
	int t=dijkstra();
	cout<<t<<endl;
	return 0;
}

2、存在负边权
①Bellman-Ford(适用于有限制边数的情况)O(nm)
题意:求从点1到点n的最多经过k条边的最短路径,可能存在重边和自环,边权可能为负。
思想:通过不断更新每条边连接的两个点例如a→b,间接通过a来更新b到1的距离,所以如果不存在可以从1到n的路径,每个点也还是会被更新(只要有边连接)。

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e4+10,INF=0x3f3f3f3f;
int d[510],backup[510];
int n,m,k;
struct Edge
{
	int a,b,w;
}e[N];
int bellman_ford()
{
	memset(d,INF,sizeof(d));
	d[1]=0;//初始化勿忘
	for(int i=0;i<k;i++)
	{
		memcpy(backup,d,sizeof(d));//先备份一份d[],这样就不会修改了一条边后导致后面的也跟着变化,因为这里是有k的限制的
		for(int j=0;j<m;j++)
		{
			int a=e[j].a,b=e[j].b,w=e[j].w;
			d[b]=min(d[b],backup[a]+w);
		}
	}
	if(d[n]>INF/2) return INF;//为什么不能是d[n]==INF,因为有可能存在从1到达不了n,但是有其他的
	//点可以到达n(边权为负),此时d[n]是被更新了的,不再是INF。
	return d[n];
}
int main()
{
	cin>>n>>m>>k;
	for(int i=0;i<m;i++)
		cin>>e[i].a>>e[i].b>>e[i].w;
	int t=bellman_ford();
	if(t==INF)
		cout<<"impossible"<<endl;
	else
		cout<<t<<endl;

	return 0;
}

② spfa算法(堆bellman_frod的优化)

思路:它只遍历当前被修改了的点的所有后继结点的权值并把他们入队,其他没有发生变化的就先不遍历。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10,INF=0x3f3f3f3f;
int e[N],w[N],ne[N],h[N],idx;
int d[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
	memset(d,INF,sizeof(d));
	d[1]=0;
	queue<int> q;
	q.push(1);
	st[1]=true;
	while(q.size())
	{
		int t=q.front();
		q.pop();
		st[t]=false;
		for(int i=h[t];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[t]+w[i])
			{
				d[j]=d[t]+w[i];
				if(!st[j])
				{
					q.push(j);
					st[j]=true;
				}
			}
		}
	}
	return d[n];//这里可以直接返回d[n]来判断有没有路径的,因为进行更新的点都是从1开始拓展出去的
}
int main()
{
	memset(h,-1,sizeof(h));
	scanf("%d%d",&n,&m);
	int x,y,z;
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
	}
	int t=spfa();
	if(t==INF) printf("impossible\n");
	else printf("%d\n",t);
	return 0;
}

AcWing 852. spfa判断负环
思路:增加一个数组cnt[i]来记录到达i点时经过的路径数量,当cnt[i]>=n时,说明此时这条路径经过的点的数量大于n+1,所以一定有点重复了,即存在负环。为什么是负环?这里d数组不用进行初始化?他会从第一次遇到负数的边开始找起,因为只要存在负环一定可以找到一个点(这个环里的点)使往后的所有边的权和为负数,然后无线循环下去直到cnt[i]>=n。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=2010,M=1e4+10;
int h[M],e[M],w[M],ne[M],idx;
int d[N],cnt[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		q.push(i);
		st[i]=true;
	}
	while(q.size())
	{
		int t=q.front();
		q.pop();
		st[t]=false;
		for(int i=h[t];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[t]+w[i])
			{
				d[j]=d[t]+w[i];
				cnt[j]=cnt[t]+1;
				if(cnt[j]>=n) return true;//说明存在负环
				if(!st[j])
				{
					q.push(j);
					st[j]=true;
				}
			}
		}
	}
	return false;
}
int main()
{
	memset(h,-1,sizeof(h));
	cin>>n>>m;
	int x,y,z;
	for(int i=0;i<m;i++)
	{
		cin>>x>>y>>z;
		add(x,y,z);
	}
	if(spfa()) cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return 0;
}

二、多源汇最短路

①Floyd算法 O(n^3)
思路:动态规划,d[i][j]表示从i到j的最短路径,k是i到j的过程中可能经过的其它点,所以d[i][j]=min(d[i][j],d[i][k]+d[k][j])。
 

#include<iostream>
using namespace std;
const int N=210,INF=0x3f3f3f3f;
int d[N][N];
int n,m,q;
void floyd()
{
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i==j) d[i][j]=0;//自环
			else d[i][j]=INF;
		}
	int x,y,z;
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		d[x][y]=min(d[x][y],z);//重边
	}
	floyd();
	while(q--)
	{
		scanf("%d%d",&x,&y);
		if(d[x][y]>INF/2) //同样存在x到达不了y,但x,y也被更新了的情况
			printf("impossible\n");
		else printf("%d\n",d[x][y]);
	}
	return 0;
}
举报

相关推荐

0 条评论