图论——最短路问题综合(Dijkstra+Spfa+Floyd+Bellman-Ford)

在这里插入图片描述最短路问题大致分为这几个情景和算法,主要就是单源或者多源,以及是否存在负权边。今天刚刚学完,来总结一下。

1.Dijkstra算法

#include <stdio.h>
#include <string.h>
# define N 510 

int n,m;
int g[N][N];
int dist[N];
bool st[N];

int min(int a,int b)
{
	return a<b?a:b;
}

int dijkstra()
{
	memset(st,0,sizeof st);//初始化访问数组 记录是否访问过
	memset(dist,0x3f,sizeof dist);//dist[i]为起点到i点的距离 初始化为正无穷
	dist[1]=0;//初始化起点距离为0
	
	for(int i=0;i<n;i++)//通过循环 找到距离起点最近的点
	{
		int t=-1;
		for(int j=1;j<=n;j++)
			if(!st[j]&&(t==-1||dist[t]>dist[j]))
			t=j;
		st[t]=true;//记录访问过 以后不再访问
		
		for(int j=1;j<=n;j++)//松弛操作
			dist[j]=min(dist[j],dist[t]+g[t][j]);
	}
	return dist[n];
}
int main()
{
	while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){
	memset(g,0x3f,sizeof g);
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		g[a][b]=min(g[a][b],c);//若两个点有多条路 保留距离最短的路
		//若题目给的无向边
		则g[a][b]=g[b][a]min(g[a][b],c);
	}
	int t=dijkstra();
		printf("%d\n",t);
}
return 0;
	
}

2.Bellman-Ford算法(有边数限制的最短路)

若边中,有权重为负数或题目中要求“请你求出从1号点到n号点的最多经过k条边的最短距离”之类的限定经过边数的语言,则应使用此算法。
存储数据时用结构体较方便。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, M = 10010;

struct Edge//存储数据用结构体
{
    int a, b, c;
}edges[M];

int n, m, k;
int dist[N];
int last[N];

void bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);//初始化

    dist[1] = 0; 
    for (int i = 0; i < k; i ++ )
    {
        memcpy(last, dist, sizeof dist);//备份数据
        for (int j = 0; j < m; j ++ )
        {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);//由于题目限制了经过k条边 固为了防止串连 需要用备份数据进行松弛操作
        }
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }

    bellman_ford();

    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");//因为存在负边,故更新时可能会小于正无穷 
    else printf("%d\n", dist[n]);

    return 0;
}


3.Spfa算法求最短路

Spfa算法单源最短路径的一种算法,它是Bellman-ford的队列优化
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。

但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。

实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

此外,SPFA算法还可以判断图中是否有负权环,即一个点入队次数超过N。
————————————————
以上内容摘自CSDN博主「算法之心」

#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>

using namespace std;

const int N=100010;

int n,m;
int h[N],w[N],e[N],ne[N],idx;//链表存储数据m
int dist[N];
bool st[N];

void add(int a,int b,int c)//在a,b点之间插入长度为c的点
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int spfa()
{
	memset(dist,0x3f,sizeof(dist));
	dist[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])//从t点向与他相邻的节点遍历
		{
			int j=e[i];
			if(dist[j]>dist[t]+w[i])
			{
				dist[j]=dist[t]+w[i];//更新距离
				if(!st[j])//未在队列
				{
					q.push(j);入队
					st[j]=true;
				}
			}
		}
	}
	return dist[n] ;
}
int main()
{
	scanf("%d%d",&n,&m);
	
	memset(h,-1,sizeof(h));
	
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	int t=spfa();
	
	if (t == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", t);
	return 0;
}

4.Spfa 判断负环

通过spfa判断是否有负环可以通过某两个点的最短距离之间需要经过的边数k是否大于等于点的数量n,根据抽屉原理,若设1-x点之间经历了n条边,则1-x路径上至少经历了n+1个点,一共只有n个点,故一定有两个点是相同的,故存在负环。

#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>

using namespace std;

const int N=10010;

int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N],cnt[N];
bool st[N];

void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int spfa()
{
	queue<int> q;
 for (int i = 1; i <= n; i ++ )//因为负环不一定经过点1 故初始化使全部点入队
    {
        st[i] = true;
        q.push(i);
    }
	
	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(dist[j]>dist[t]+w[i])
			{
				dist[j]=dist[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()
{
	scanf("%d%d",&n,&m);
	
	memset(h,-1,sizeof(h));
	
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	if(spfa())
	printf("Yes\n");
	else
	printf("No\n");
	
	return 0;
}

5.Floyd算法(多源最短路)

Floyd算法也叫插点法,实则利用动态规划思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra相似,不过是多源,储存数据用邻接矩阵储存。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N=210 ,INF= 1e9;

int n,m,Q;
int d[N][N];

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;
 	while(m--)
 	{
 		int a,b,c;
 		scanf("%d%d%d",&a,&b,&c);
 		d[a][b]=min(d[a][b],c);
	 }
	 floyd();
	 while(Q--)
	 {
	 	int a,b;
	 	scanf("%d%d",&a,&b);
	 	
	 	int t=d[a][b];
	 	if(t>INF/2) puts("impossible");
	 	else printf("%d\n",t);
	 }
	 return 0;
 }

大概总结一下,堆优化的dij随后会补上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值