详解五种最短路径算法及其区别(c++)

 

目录

一.朴素Dijkstra算法

二.堆优化的Dijkstra

三.bellman_ford算法

四.spfa算法

五.floyd算法


使用区别:

所有边权都是正数的单源最短路:朴素Dijkstra算法,堆优化的Dijstra算法。

存在负权边的单源最短路:Bellman-ford,spfa。

多源汇最短路:floyd算法。​​​​​​​

总结: 

朴素Dijkstra算法: 适用于稠密图,适合邻接矩阵存储,时间复杂是 O(n^2+m), n表示点数,m  表示边数。

堆优化的Dijkstra算法:适用于稀疏图,适合邻接表存储,时间复杂度是O(mlogn)

Bell-Ford算法:  适用于处理可能存在负环的有限路线单源最短路问题,时间复杂度是O(nm),一般不能有负权回路。

spfa算法:时间复杂度是O(m)最坏O(nm)。

floyd算法:时间复杂度O(n^3)。

一.朴素Dijkstra算法

简介:迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

算法思路:

按路径长度递增次序产生算法:

把顶点集合V分成两组: 

(1)S:已求出的顶点的集合(初始时只含有源点V0) 

(2)V-S=T:尚未确定的顶点集合 

将T中顶点按递增的次序加入到S中,保证: 

(1)从源点V0到S中其他各顶点的长度都不大于从V0到T中任何顶点的最短路径长度 

(2)每个顶点对应一个距离值 

S中顶点:从V0到此顶点的长度 

T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度 

依据:可以证明V0到T中顶点Vk的,或是从V0到Vk的直接路径的权值;或是从V0经S中顶点到Vk的路径权值之和。

算法步骤如下 

G={V,E}

1. 初始时令 S={V0},T=V-S={其余顶点},T中顶点对应的距离值 

若存在,d(V0,Vi)为弧上的权值 

若不存在,d(V0,Vi)为∞ 

2. 从T中选取一个与S中顶点有关联边且权值最小的顶点W,加入到S中

3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值

重复上述步骤2、3,直到S [1]  中包含所有顶点,即W=Vi为止 

 简单地说就是先找出一个距离起点最近的,然后再由这个距离起点最近的点更新别的点

题目:https://www.acwing.com/problem/content/851/

 

 代码如下:

#include<bits/stdc++.h>
using namespace std;
//dijkstra算法适用于正权边稠密图 
const int N = 510;
int n,m;
int dist[N],g[N][N];//存放距离和图 
bool st[N];//存放状态 

int dijkstra()
{
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
	//遍历n次,将n个点的最值都找出
	for(int i=0;i<n-1;i++)
	{
		int t = -1;//点 
		//从不确定的点中找出距离一号点最短的点
		for(int j=1;j<=n;j++)
		{
			if(!st[j]&&(t==-1||dist[t]>dist[j])) 
				t = j;//依次比较距离1号点最近的点赋给t 
		} 
		//根据t更新t的出边
		for(int j = 1;j<=n;j++)
		{
			dist[j] = min(dist[j],dist[t]+g[t][j]);
		} 
		//表示这个点已经确定了 
		st[t] = true;
	} 
	//如果没有边就是无穷 
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

int main()
{
	cin>>n>>m;
	memset(g,0x3f,sizeof g);
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b] = min(g[a][b],c);//因为有重边,取一条最短的边 
	}
	int t = dijkstra();
	cout<<t<<" ";
	return 0;
}

二.堆优化的Dijkstra

简介:在确定一个还未确定最短距离的点中距离源点最近距离的点时 朴素Dijkstra 的方法是遍历所有的点通过比较找出最近的点,在这个地方可以使用 优先队列(小根堆) 来进行优化,在所有的点中取出距离最小的点。

算法思路:

首先,将 优先队列 定义成 小根堆,将源点的距离初始化为 0 加入到优先队列中。

然后,从这个点开始扩展。先将队列中的队头元素 ver 保存到一个临时变量中,并将队头元素出队,然后遍历这个点的所有出边所到达的点 j,更新所有到达的点距离源点最近的距离。

如果源点直接到 j 点的距离比源点先到 ver 点再从 ver 点到 j 点的距离大,那么就更新 dist[j],使 dist[j] 到源点的距离最短,并将该点到源点的距离以及该点的编号作为一个 pair 加入到优先队列中,然后将其标记,表示该点已经确定最短距离。因为是小根堆,所以会根据距离进行排序,距离最短的点总是位于队头。一直扩展下去,直到队列为空。

因为有 重边 的缘故,所以该点可能会有冗余数据,即如果在扩展的时候,第一次遍历到的点是 2 号点,距离 源点 的距离为 10,此时 dist[2] = 0x3f3f3f3f > dist[1] + distance[1 -> 2] = 0 + 10 = 10 所以 dist[2] 会被更新为 10,此时会将 {10, 2} 入队。但是很不巧从 源点 到 2 号点有一个距离为 6 的重边,当遍历到这个重边时,由于 dist[2] = 10 > dist[1] + distance[1 -> 2] = 0 + 6 = 6,所以 {6, 2} 也入队了,入队之后由于是 小根堆 所以 {6, 2} 会排在 {10, 2} 前面,所以 {6, 2} 会先出队,出队之后会被标记。所以当下一次再遇到已经被标记的 2 号点时,直接 continue 忽略掉冗余数据继续扩展下一个点即可。
 

题目:https://www.acwing.com/problem/content/852/

 

 代码如下:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+10;
int h[N],e[N],ne[N],w[N],idx;//稀疏图采用邻接表存储 
typedef pair<int,int> p;//存结点距离和编号 
int dist[N];
int n,m;
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 dijkstra()
{
	priority_queue<p,vector<p>,greater<p> >  q;
	memset(dist,0x3f,sizeof dist);
	q.push({0,1});
	dist[1] = 0;
	while(!q.empty())
	{
		p t = q.top();
		q.pop();
		int ver = t.second,dis = t.first;
		//如果此节点已经确定最小值就continue 
		if(st[ver]) continue;
		st[ver] = true;
		//else就拓展此节点,根据t更新t的拓展节点
		for(int i = h[ver];i!=-1;i=ne[i])
		{
			int j = e[i];
			if(dist[j]>dist[ver]+w[i]) 
			{
				dist[j] = dist[ver]+w[i];
				q.push({dist[j],j});
			}	
		} 
		
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	cout<<dijkstra()<<" "; 
	
	return 0;
} 

三.bellman_ford算法

简介:贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼和莱斯特·福特创立的,求解单源最短路径问题的一种算法。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,最坏O(nm).

Bellman-Ford算法是一种处理存在负权边的单元最短路问题的算法。解决了Dijkstra无法求的存在负权边的问题。 虽然其算法效率不高,但是也有其特别的用处。其实现方式是通过m次迭代求出从源点到终点不超过m条边构成的最短路的路径。一般情况下要求途中不存在负环。但是在边数有限制的情况下允许存在负环。因此Bellman-Ford算法是可以用来判断负环的。很重要的一点是本算法每次迭代都是在上一次的基础上进行的,因此我们在代码实现时要注意保留上一次的结果,在上一次的基础上算,故需要将上次的数组copy一份。

模板题目https://www.acwing.com/problem/content/855/

​​​​​​​

 

代码如下:

#include<bits/stdc++.h>

using namespace std;

const int N = 510,M = 10010;
int dist[N],backup[N];
int n,m,k;
struct Edge{
	int a,b,c;
}edges[M];

void bellman_ford()
{
    memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
    //k表示从i到j经过k条边
	for(int i=0;i<k;i++)
	{
		memcpy(backup,dist,sizeof dist);
		for(int j=0;j<m;j++)
		{
			int a = edges[j].a , b = edges[j].b , c = edges[j].c;
            //当dist[b]有值时,表示以b为尾的这条边更新完了
			dist[b] = min(dist[b],backup[a]+c);
		}
	}
	
}

int main()
{
    cin>>n>>m>>k;
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>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;
}

四.spfa算法

简介: SPFA 算法是Bellman-Ford算法的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素Bellman-Ford相同,为O(nm)

优化思路:

Bellman-Ford算法在每次更新时,遍历了所有的边,而也如前面看到的,每次遍历未必会真的更新最短距离。SPFA算法就是对该步骤的优化。每次只有当前的起点到源点的距离变小了,该点连通的其他点才会变小。
只要一个结点的边变小了,我们就把他放进队列(其他的也行)中,只要队列中还有值,我们就继续更新,更新到的点也加入队列,如此往复,直到标记全部的点。

题目:https://www.acwing.com/problem/content/853/

 

 代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;

int h[N],e[N],w[N],ne[N],idx;
int dist[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()
{
	queue<int> q;
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
	q.push(1);
	st[1] = true;
	while(!q.empty())
	{
		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];
				if(!st[j])
				{
					q.push(j);
					st[j] = true;
					
				}
			}
		}
	}
	
	return dist[n];
}

int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	int t = spfa();
	if(t==0x3f3f3f3f) cout<<"impossible"<<endl;
	else cout<<t<<endl;
	return 0;
} 

五.floyd算法

简介:弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包,Floyd 算法是一个基于贪心、动态规划求一个图中 所有点到所有点 最短路径的算法。

算法思路:

以每个点为「中转站」,刷新所有「入度」和「出度」的距离。每次从「未求出最短路径」的点中 取出 最短路径的点,并通过这个点为「中转站」刷新剩下「未求出最短路径」的距离。简单地说就是dist[i,j]表示从i到j经过k这个点的最短距离是多少。

 题目:https://www.acwing.com/problem/content/853/

 代码如下:

#include<bits/stdc++.h>
using namespace std;

const int N = 210,M = 20010;
int dist[N][M];
int n,m,k;

void floyd()
{
//以k这个点作为中转点更新从i到j的距离
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            //比较从i直接到j的距离和从i到j以k为中转的距离最小值
            dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]);
        }
    }
}

int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(i==j) dist[i][j]=0;
            else dist[i][j] = 0x3f3f3f3f;
        }
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        dist[x][y] = min(dist[x][y],z);
    }
    floyd();
    while(k--)
    {
        int x,y;
        cin>>x>>y;
        if(dist[x][y]>0x3f3f3f3f/2) cout<<"impossible"<<endl;
        else cout<<dist[x][y]<<endl;
    }
    return 0;
}

  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ranyh524

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值