首页 > 其他分享 >最短路

最短路

时间:2024-02-27 18:03:46浏览次数:26  
标签:int 短路 路径 Bellman 算法 dp dis

1 算法描述

在一图中,从一点出发,沿图的边走到另一点所经过的路径中,各边上权值和最小的路径,叫做最短路径。最短路算法就是求解最短路径问题的算法。

其中,单源最短路径指从图中某一点到另外所有点的最短路径;多源最短路径指从图中每一点到另外所有点的最短路径。

2 四大最短路算法

2.1 Floyd 算法

Floyd 是一种多源最短路算法,其思想是动态规划。

设 $dp[i][j][k]$ 表示从 $i$ 到 $j$ 的只以 $1 - k$ 中的节点为中间节点的最短路径的长度。

若最短路径经过点 $k$,则 $dp[i][j][k] = dp[i][k][k - 1] + dp[k][j][k - 1]$;

若最短路径不经过点 $k$,则 $dp[i][j][k] = dp[i][j][k - 1]$。

综上,$dp[i][j][k] = min{ dp[i][k][k - 1] + dp[k][j][k - 1], dp[i][j][k - 1]}$。

实际中,常将 dp 数组降至二维使用

Floyd 的时间复杂度为 $O(n3)$,空间复杂度为$O(n2)$,容易超时。

核心代码如下(使用邻接矩阵存图):

int a[Maxn][Maxn];
for(int k = 1; k <= n; k++)//Floyd
{
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
		}
	}
}

2.2 dijkstra 算法

dijkstra 是一种单源最短路算法,适用于边权值为正的情况。

它的思想是从源点 $S$ 开始,每次新扩展一个距离最近的点,再以这个点为中间点,更新起点到其它点的距离。

算法步骤如下:

设 $dis[i]$ 表示 源点 $s$ 到点 $i$ 的最短距离。

第一步,将 $dis[s]$ 置为 $0$,其余点 $dis$ 值为 $\infty $。

第二步,选一个 $dis$ 值最小的点,并标记已知点,用它来更新与之相邻的其它点的dis值。

重复第二步,直到所有点都被标记。

dijkstra 的正确性说明如下:

每次选取 $dis$ 值最小的点标记为已知,是因为其他点的 $dis$ 值更大,而又没有负边权,所以不可能对选取的这个点的 $dis$ 值更新。也就是说选取的这个点的 $dis$ 值不可能被修改,就是最短的了

朴素 dijkstra 的时间复杂度为 $O(N^2)$,是较为快速且较为稳定的最短路算法,缺点是不能处理负边权。

观察上述算法步骤,发现第二步中寻找最小值的部分可以用堆来优化,可以将时间复杂度降至 $O((m+n)\log n)$.

堆优化算法如下(使用链式前向星存图):

int s, dis[Maxn], vis[Maxn];
priority_queue <pair<int, int> > q;//堆

void dijkstra()
{
	for(int i = 1; i <= n; i++)//初始化
	{
		dis[i] = 2e9;
	}
	dis[s] = 0;
	q.push(make_pair(0, s));
	while(!q.empty())//dijkstra
	{
		int x = q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		for(int i = head[x]; i; i = edge[i].nxt)
		{
			if(dis[edge[i].to] > dis[x] + edge[i].w)
			{
				dis[edge[i].to] = dis[x] + edge[i].w;
				q.push(make_pair(-dis[edge[i].to], edge[i].to));
			}
		}
	}
}

3.3 Bellman - Ford 及 SPFA 算法

Bellman - Ford 是一种单源最短路算法,它可以适用于带负权边的图。SPFA 则是 Bellman - Ford 的队列优化算法。

Bellman - Ford 的算法流程如下:

以任意顺序考虑图的边,沿着各条边进行松弛操作。即对于边 $u$ 到 $v$,如果 $d[v]>d[u]+w[u][v]$, 则 $d[v]=d[u]+w[u][v]$。重复操作 $|V|-1$ 次

(其中 $|V|$ 表示图中顶点的个数,$d[i]$ 表示 源点 $s$ 到点 $i$ 的最短距离,$w[u][v]$ 表示点 $u$ 到点 $v$ 的距离)。

最后再对各条边进行松弛操作,如果对于边边 $u$ 到 $v$,还存在 $d[v]>d[u]+w[u][v]$,则说明存在负权环。

Bellman - Ford 的正确性说明如下:

图的任意一条最短路径既不能包含负权回路(无解),也不会包含正权回路(不是最优解),因此它最多包含 $|V|-1$ 条边。

从源点 $s$ 可到达的所有顶点如果存在最短路径,则这些最短路径会构成一个以 $s$ 为根的最短路径树。Bellman-ford 算法实际上是逐层生成这棵树的过程。

第一轮松弛,就生成了距离 $s$ 层次至多为 $1$ 的树枝;每一轮松弛都是利用上一轮松弛之后的结果,每一轮松弛,都会有一层节点达到从 $s$ 到它们的最短距离,并且不会受到后续操作的影响。

$|V|-1$ 轮松弛后,就得到了 $s$ 到其余顶点的最短路(除非图中存在负权环),因此,判定是否存在负权环,我们只需要再松弛一遍就可以知道了。

Bellman - Ford 时间复杂度为 $O(VE)$,优点是可以适用于带负权的图。

核心代码如下(使用邻接矩阵存图):

int dis[Maxn];
bool bellman_ford(int s)
{
	for(int i = 1; i <= n; i++)
		dis[i] = INF;
	dis[s] = 0;
	for(int i = 1; i <= n - 1; i++)//松弛
		for(int j = 1; j <= m; j++)
			if(dis[edge[j].u] + edge[j].w < dis[edge[j].v]])
				dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
	for(int j = 1; j <= m; j++)//判负权环
			if(dis[edge[j].u] + edge[j].w < dis[edge[j].v]])
				return 0;
	return 1;		
}

Bellman - ford 算法效率低在无效松弛操作太多,而 SPFA 使用队列优化了 Bellman - Ford 的效率。

SPFA 算法流程如下:

设一队列 $q$ ,将源点 $s$ 入队。

从队列中取出队首元素 $u$ ,标记节点 $u$ 出队,对 $u$ 相连的所有节点 $v$ 进行松弛操作。如果松弛成功,检查节点 $v$ 进队次数,如果超过 $|V|$,则说明出现负权环,算法结束;否则,修改 $d[v]$,检查节点 $v$ 是否在队列中,如果不在,节点$v$进队。

重复上述过程直到队列为空。

SPFA时间复杂度为 $O(kE)$,但是较为不稳定,最坏情况下会被卡到 $O(VE)$。

核心代码如下:

int dis[Maxn], vis[Maxn], t[Maxn];
queue <int> q;
bool SPFA(int s)
{
	for(int i = 1; i <= n; i++)
		dis[i] = INF;
	dis[s] = 0;
	q.push(s);
	vis[s] = 1;
	while(!q.empty())
	{
		int now = q.front();
		q.pop();
		if(++t[now] == n)
		{
			return 0;
		} 
		vis[now] = 0;
		for(int i = 0; i < E[now].size(); i++)
		{
			int v = E[now][i].first;
			if(dis[v] > dis[now] + E[now][i].second)
			{
				dis[v] = dis[now] + E[now][i].second;
				if(vis[v] == 1) continue;
				vis[v] = 1;
				q.push(v);
			}
		}
	} 
	return 1;		
}

3 总结

四种算法各有优缺点,需要根据题目需求来决定使用哪种。

Floyd dijkstra Bellman - Ford SPFA
源点 多源 单源 单源 单源
空间复杂度 $O(n^2)$ $O(m)$ $O(m)$ $O(m)$
时间复杂度 $O(n^3)$ $O((m+n)\log n)$ $O(mn)$ $O(km)$,易被卡到 $O(nm)$
能否有负权边 可以 不可以 可以 可以
能否判断负权回路 不可以 不可以 可以 可以

标签:int,短路,路径,Bellman,算法,dp,dis
From: https://www.cnblogs.com/dzbblog/p/18037446

相关文章

  • && 短路效果测试
    C#:staticvoidMain(string[]args){boolresult=true;result&=Func();result&=Func();result&=Func();Console.WriteLine("&=最后结果:{0}\n",result);Console.ReadKey();result=result......
  • SPFA最短路
    目录从Bellman-Ford开始核心思想模拟算法执行过程时间复杂度模板spfaspfa优化的思想模板从Bellman-Ford开始对于所有边权都大于等于0的图,任意两个顶点之间的最短路,显然不会经过重复的顶点或者边。也就是说任意一条最短路经过的定点数不会超过n个,边不会超过n-1条边而对于有边权......
  • 分层图最短路
    分层最短路用更加具体或者形象一点的说法就是有限制的最短路径问题。通常是拆点解决问题,原图中的点加上一个当前点的状态,成为一个新的点P4568[JLOI2011]飞行路线P4568[JLOI2011]飞行路线#include<bits/stdc++.h>#defineintlonglong#definerep(i,a,b)for(inti=......
  • 短路在JavaScript中是如何工作的?
    在JavaScript中,理解真实和虚假的值是编写高效简洁代码的基础。结合短路的概念,开发人员可以编写优雅的解决方案来应对常见的编程挑战。在本实践指南中,我们将探讨真实值和虚假值,并了解JavaScript中短路的机制。您可以从这里获取所有源代码。(本文内容参考:java567.com)目录了......
  • 最短路学习笔记
    最短路学习笔记单源最短路径问题(SingleSourceShortestPath,SSSP问题)。Part1DijkstraPart1.0Dijkstra朴素算法洛谷P3371【模板】单源最短路径(弱化版)Dijkstra算法Dijkstra算法的流程如下:初始化$dist[1]=0$,其余节点的$dist$值为正无穷大。找出一个未被标记的......
  • 最短路径
    最短路算法主要有Floyd,Dijkstra,Bellman-Ford,SPFA,Jahnson,A*这六种算法。Floyd用来处理全源最短路,可以处理负边权。时间复杂度为\(\mathcal{O}(N^3)\)。Dijkstra用来处理单源最短路,不能处理负边权。时间复杂度最优为\(\mathcal{O}{(M\logM)}\)。Bellman-Ford用来处理单......
  • 次短路径问题
    一、问题描述P2865[USACO06NOV]RoadblocksG二、问题简析如果求最短路径,我们很自然会想到\(Dijkstra\)。但是,这道题要求的是次短路径。记到\(u\)的最短路径为\(d_1[u]\),到\(u\)的次短路径为\(d_2[u]\)。则\(d_2[v]=d_1[u]+e(u,v)\)or\(d_2[u]+e(u,v).w\)......
  • 最短路径问题
    一、Bellman-Ford算法Q:有一张有\(n\)个点、\(m\)条边的有向图,可能存在重边、负边和自环,但不存在负环,求起点\(s\)到每个点的最短路径。1.1算法简析记图为\(G\);\(G[u]\)表示以\(u\)为起点的所有边的集合;\(e(u,v)\)表示\(u\)到\(v\)的某一条有向边(\(e(u,v)\inG[......
  • 单源正权最短路径——Dijkstra算法
    目录问题引入思路一览算法原理code问题引入给出n点m边,其中边的权值皆为非负数,要求快速给出一个点到其余点的最短距离思路一览dfs遍历维护最短距离floyd算法算全源最短,但是这玩意时间复杂度O(n3),最多也就1000个点左右主角Dijkstra算法算法原理主要是以源点为根节点逐步构......
  • [数论学习笔记01]完系/同余最短路/费马小定理/扩欧/中国剩余定理
    #[数论学习笔记01]完系/同余最短路/费马小定理/扩欧/中国剩余定理###每日蒟蒻小故事(1/1)蒟蒻带了一本崭新的笔记本到S组.他发现这一节课居然在学习数论."听不懂,求讲解!"蒟蒻说.大佬邪魅一笑,并未理会.蒟蒻只能一边听着老师的讲解,一边努力地记着笔记."什么是完全剩余系......