最短路径快速算法 (英語:Shortest Path Faster Algorithm (SPFA) ),国际上一般认为是带有队列优化的Bellman-Ford 算法 ,一般仅在中国大陆被称为SPFA ,是一个用于求解有向带权图单源最短路径的算法。这一算法在随机的稀疏图上表现出色,并且适用于带有负边权的图。[ 1] 然而SPFA在最坏情况的时间复杂度与 Bellman-Ford 算法相同,因此在非负边权的图中使用堆优化的Dijkstra 算法 效率可能优于SPFA。[ 2] SPFA算法首先在1959年由Edward F. Moore 作为广度优先搜索 的扩展发表[ 3] ,相同算法在1994年由段凡丁重新发现。[ 4]
算法
给定一个有向带权图
G
=
(
V
,
E
)
{\displaystyle G=(V,E)}
和一个源点
s
{\displaystyle s}
,SPFA算法可以计算从
s
{\displaystyle s}
到图中每个节点
v
{\displaystyle v}
的最短路径。其基本思路与 Bellman-Ford 算法相同,即每个节点都被用作用于松弛其相邻节点的备选节点。但相较于 Bellman-Ford 算法,SPFA算法的先进之处在于它并不盲目地尝试所有节点,而是维护一个备选的节点队列,并且仅有节点被松弛后才会将其放入队列中。整个流程不断重复,直至没有节点可以被松弛。
下面是这个算法的伪代码。[ 5] 这里的
Q
{\displaystyle Q}
是一个备选节点的先进先出队列,
w
(
u
,
v
)
{\displaystyle w(u,v)}
是边
(
u
,
v
)
{\displaystyle (u,v)}
的权值。
一个基于欧氏几何距离的SPFA算法。红线是当前状态下的各条最短路径。蓝线表示松弛发生的地方,也即通过在
Q
{\displaystyle Q}
中用节点
u
{\displaystyle u}
连接
v
{\displaystyle v}
可以给出一条从源点到
v
{\displaystyle v}
更短的路径
procedure Shortest-Path-Faster-Algorithm(G , s )
1 for each vertex v ≠ s in V (G )
2 d(v ) := ∞
3 d(s ) := 0
4 offer s into Q
5 while Q is not empty
6 u := poll Q
7 for each edge (u , v ) in E (G )
8 if d(u ) + w(u , v ) < d(v ) then
9 d(v ) := d(u ) + w(u , v )
10 if v is not in Q then
11 offer v into Q
对于无向图,将每条无向边视作两条有向边以采用 SPFA 算法。
最坏情况下的性能
下面是一种触发该算法低性能表现的数据构造方式。假设要求从节点1到节点
n
{\displaystyle n}
的最短路径。对于整数
1
≤ ≤ -->
i
<
n
{\displaystyle 1\leq i<n}
,考虑添加边
(
i
,
i
+
1
)
{\displaystyle (i,i+1)}
并令其权为一个随机的小数字(于是最短路应为1-2-...-
n
{\displaystyle n}
),同时随机添加
4
n
{\displaystyle 4n}
条其他的权较大的边。在这种情况下,SPFA算法的性能表现将会非常低下。[ 1]
SPFA算法本质上依然被认为是Bellman-Ford算法的一个特例,因此一般认为SPFA算法的最差复杂度是
O
(
|
V
|
⋅ ⋅ -->
|
E
|
)
{\displaystyle O(|V|\cdot |E|)}
,其中
|
V
|
{\displaystyle |V|}
为点数,
|
E
|
{\displaystyle |E|}
为边数。[ 1]
NOI 2018中,出题人使用特殊构造图卡到SPFA算法的最坏情况,并在讲题时在幻灯片上打出关于“SPFA它死了”的字样,导致现今很多中国大陆OI题目都会构造特殊数据卡掉SPFA,于是关于SPFA已死的说法广泛流传于中国大陆。[原創研究?]
优化技巧
SPFA算法的性能很大程度上取决于用于松弛其他节点的备选节点的顺序。事实上,如果
Q
{\displaystyle Q}
是一个优先队列,则这个算法将很类似于Dijkstra 算法。然而尽管这一算法中并没有用到优先队列,仍有多种可用的技巧可以用来提升队列的质量,借此能够提高平均性能(但仍无法提高最坏情况下的性能)。其中,最著名的两种技巧通过重新调整
Q
{\displaystyle Q}
中元素的顺序从而使得更靠近源点的节点能够被更早地处理。因此一旦实现了这两种技巧,
Q
{\displaystyle Q}
将不再是一个先进先出队列,而更像一个链表或双端队列。
距离小者优先 (Small Label First (SLF ))(由Bertsekas在Networks, 第23期, 1993, P703-P709中最先提出)。在伪代码的第十一行,将总是把
v
{\displaystyle v}
压入队列尾端修改为比较
d
(
v
)
{\displaystyle d(v)}
和
d
(
front
(
Q
)
)
{\displaystyle d{\big (}{\text{front}}(Q){\big )}}
,并且在
d
(
v
)
{\displaystyle d(v)}
较小时将
v
{\displaystyle v}
压入队列的头端。这一技巧的伪代码如下(这部分代码插入在上面的伪代码的第十一行后):
procedure Small-Label-First(G , Q )
if d(back(Q )) < d(front(Q )) then
u := pop back of Q
push u into front of Q
距离大者置后 (Large Label Last (LLL ))(由Bertsekas、Guerriero、与Musmanno在JOTA, 第88期, 1996, 页297-320最先提出)。在伪代码的第十一行,我们更新队列以确保队列头端的节点的距离总小于平均,并且任何距离大于平均的节点都将被移到队列尾端。伪代码如下:
procedure Large-Label-Last(G , Q )
x := average of d(v ) for all v in Q
while d(front(Q )) > x
u := pop front of Q
push u to back of Q
参考文献
扩展阅读
夏正冬; 卜天明; 张居阳. SPFA算法的分析及改进 . 《计算机科学》. 2014, 41 (6): 180–184 [2020-11-17 ] . (原始内容存档 于2020-12-08).