关于最短路

关于最短路,大家应该都知道有Dijkstra,SPFA以及Floyd。

此处先提出一个问题:

给定图G,每条边有边权。
求从一点到另一点的边权和最小的路径。
要求图中不能有负回路(否则为NP问题)。

首先提到的便是Floyd。

如果数据范围足够小,相信大家大部分会选择Floyd(为什么呢,后文解释)。

大家都知道Floyd板子好背,不像其它两种算法一样颇为复杂。

但别看它貌似简单,但其实蕴含着大道理,这也是我去清北学堂的时候听hzc老师讲的。

Floyd的储存方法就是用邻接矩阵储存,比较方便。

它的原理是DP,时间复杂度为O(n3),空间复杂度为O(n2)。

所以Floyd是不可以用来做数据范围较大的题的。

令f[i][j][k]为从i到j,中途只经过编号1~k的节点的最短路。

考虑是否经过k:

  如果经过,那么f[i][j][k]=f[i][k][k-1]+f[k][j][k-1];

  否则,f[i][j][k]=f[i][j][k-1];

直接这样做的空间复杂度是O(n3),我们可以进一步优化。

首先,f[..][..][k]只从f[..][..][k]转移,可以用滚动数组优化。

进一步地,如果经过k,那么只会从fp..[[k][k-1]转移,而f[k][..][k]和f[..][k][k]必然不会这样转移,故可以直接在原数组上迭代。

代码实现:

f[i][j]的初始值需设为极大值,如果i与j之间有边那么f[i][j]为最小的边权。

拓展应用:

求图中的最小环。先考虑无向图。

Floyd算法保证了最外层循环到|的时候所有点对之间的最短路只经过1 ∼k− 1号节点。

环至少有3个节点,设编号最大的为x,与之直接相连的两个节点为u和v。

环的长度应为f[u][v][x-1]+w[v][x]+w[x][u]。其中w为边权,如不存在边则为无穷大。

我们只要进行第x次迭代之前枚举所有编号小于k的点对更新答案即可。

代码实现:

j只循环到i-1,因为无向图i到j与j到i是等价的。

有向图则类似,另外注意两个点的环的情况。

接下来我们来看SPFA(Shortest Path Faster Algorithm)

即队列优化的Bellman-Ford算法,同样用于求单源最短路,可以用于带负权的图。

SPFA算法:

算法流程大致如下:

1.记S为起点,置ds=0;对于其它点x,置dx=∞;

2.维护一个队列,初始为空;加入S;

3.取出队首节点,记为u;

4.枚举所有与u相邻的节点v,用du+w(u,v)更新dv;如果dv被更新且v不在队列中,则将其加入队列;

5.如果队列不为空,返回3;否则算法结束。

代码实现

算法复杂度最坏情况下是O(nm)。

但一般来说,除非是故意卡SPFA,否则在效率上是和Dijkstra差不多的。

关于SPFA的优化:

1.SLF(smallest label first):如果加入的元素距离小于队首元素,则置于队首;

2.LLL(largest label last):记录队列中的元素距离平均值,从队首取元素时,如果距离大于平均值则移至队尾,重复直到找到小于等于平均值的元素为止;

3.随机化:从队首取元素时,有p的概率直接将其移至队尾;p应当是于图规模有关的一个小概率。

其实这些优化不能优化复杂度,甚至可能使算法变慢,但能在一定程度上化解针对性数据。

既然上文提到了Dijkstra,那么我们接下来自然要讲它。

首先我们来想一下宽度优先搜索

如果所有边权均为1,宽度优先搜索可以求出最短路。

如果不为1?则拆点。

用于求从图中一点出发到其它所有点的最短路。即SSSP(single source
shortest path,单源最短路)。
时间复杂度O(n2 ),空间复杂度O(m)(不含图结构)。
要求所有权值非负。称这样的图为正权图。
在下面的分析中,视1号点为源点,记du为1号点到点u的最短路距离。

我们首先来看一些正权图的性质:
1. 如果1到u的最短路上包含v,那么这条路径上1到v的部分为1到v的最短路。
2. 任意一条最短路的一部分的长度一定不大于整条路径的长度。

Dijkstra算法
算法流程大致如下:
1. 记S为起点,置ds = 0;对于其它点x,置dx = ∞;

2. 找到当前尚未处理过的点中距离最小的,记为u;
3. 枚举所有与u相邻的节点v,用du + w(u,v)更新dv
4. 标记u为已处理;
5. 如果还有尚未处理过的点,返回2;否则算法结束。

 代码实现:

关于优化:

首先改变储存图的方式;

再者,我们考虑优化循环,外层循环无法优化,则内层为先找最小值,更新答案。

我们使用堆优化。

堆是一个支持插入元素、查询或删除最小值的数据结构。
插入、删除复杂度为O(log n) ;
查询复杂度为O(1) ;
还可以支持把一个元素改小,复杂度为O(log n)。
不需要明白具体实现,当成一个黑箱就好。

为了优化查询最小值部分,我们需要存储所有尚未处理过的点。
在更新时候,如果距离变小,则在堆中更新。
而在更新时,也只在前向星中遍历从当前点发出的边。
复杂度O(nlog n)。

另一种写法是直接往堆里加,而不是改值,适用于STL中的优先队列。

就不再具体实现了。

我在冬尽外,祈你沐艳阳。

原文地址:https://www.cnblogs.com/GTBD/p/9201359.html