任意两点最短路径:Floyd-WarShall
Floyd算法解决的问题是:对于任意两个节点s
和t
,求s
到达t
的最短路径。
Floyd算法的思想是动态规划:
- 定义
d(i, j, k)
为点i
到点j
之间,只允许借道节点1...k
的最短路径; - 初始化:
d(i,j,0) = e[i, j]
,即i
到j
之间不经过任何其他中转节点的最短路径; - 更新
dij
的公式就是d(i, j, k) = min(d(i, j, k-1), d(i, k-1, k-1) + e[k, j])
; - 更新
n
次;
Floyd算法的时间复杂度是 O ( n 3 ) O(n^3) O(n3)
单源最短路径:Dijkstra
Dijkstra算法解决的问题是:没有负权边的情况下,从源节点s
到其他任意节点t
的路径长度。
- 维护一个
dist
数组,dist[i]
表示s
到i
的最短距离; - 对于每个元素,标记是否其
dist
已经确定不需要再更改(或者说维护两个集合,一个集合的dist
确定,另一个未确定);
Dijkstra算法是一种贪心策略:每次在未确定最短路径的节点里挑选距离s
最近的那个点,把这个点标记为已经确定的dist
,然后对从这个点出发的边进行松弛。
为了标记每个点,使用一个bool
数组表示:determined[i]
为true
表示i
的dist
已经是最短路径,为false
表示还不确定。
算法如下:
- 初始化
dist[i] = e[s, i]
,determined[i]
为false
; - 在
dist
未确定的元素里determined[i] == false
中寻找一个dist
最小的节点u
:- 标记
u
的dist
已经确定determined[i] = true
; - 用
u
的所有出边进行松弛:s
到i
如果经过(u, i)
这条边会不会变近?dist[i] = min(dist[i], dist[u] + e[u, i])
; - 重复循环直到所有的点都确定
dist
,重复N遍即可,每次只会确定一个新的节点的距离;
- 标记
有负权边的单源最短路径:Bellman-Ford
Dijkstra算法的缺点在于不能处理边长为负数的情况,而这就是Bellman-Ford算法解决的。
Bellman算法是一种动态规划算法(动态规划就是Bellman提出来的)。
- 定义
d(i, k)
为源点s
到i
最多经过k
条边的最短距离; - 初始化
d(i, 1) = e[s, i]
; - 每次更新
d
的公式:for all(u, v): d(v, k) = min(d(v, k-1), d(u, k-1) + e[u, v])
; - 更新
n-1
次;
启发式搜索算法:A*
A*
算法通过下面的函数来计算每个节点的优先级
f
(
n
)
=
g
(
n
)
+
h
(
n
)
f(n) = g(n) + h(n)
f(n)=g(n)+h(n)
其中
-
f
(
n
)
f(n)
f(n)是节点
n
的综合优先级,当我们选择下一个要遍历的节点时,总会选取综合优先级最高(值最小)的节点; -
g
(
n
)
g(n)
g(n)是节点
n
距离起点的代价; -
h
(
n
)
h(n)
h(n)是节点
n
距离终点的预计代价,就是A*
算法的启发函数。
A*
算法在运算过程中,每次从优先级队列中选取
f
(
n
)
f(n)
f(n)值最小(优先级最高)的节点作为下一个待遍历的节点;A*
算法使用两个集合来表示待遍历的节点与已经遍历过的节点;
A*
算法的启发式函数一般分为三种:
- 曼哈顿距离
- 对角距离;
- 欧几里得距离;
Dijkstra算法是A*算法的一个特例,其中所有节点的 h = 0 h=0 h=0;