算法设计与分析


全结点对最短路径

计算机学院    张腾

tengzhang@hust.edu.cn

问题描述

给定带权有向图$\Gcal = (\Vcal, \Ecal)$,其中$\Vcal$为边集、$\Ecal$为点集

边上的权重由函数$w: \Ecal \mapsto \Rbb$给出,允许权重为负,但没有负环

对任意一对点$u,v \in \Vcal$,求$u$$v$的最短路径


对于全结点对问题全部采用矩阵的表示形式更方便处理

$n = |\Vcal|$,点的名字就是$1, 2, \ldots, n$

邻接矩阵$\Wv = [w_{ij}]_{1 \le i,j \le n}$,其中$w_{ij} = \begin{cases} 0, & i=j \\ w(i,j), & (i,j) \in \Ecal \\ \infty, & (i,j) \not \in \Ecal \end{cases}$

动态规划

$d_v^{(k)}$:从$s$$v$经过不超过$k$条边的最短路径的长度

$$ \begin{align*} \quad d_v^{(k)} = \begin{cases} \infty, & k = 0 \\ \min \{ d_v^{(k-1)}, ~ \min_{u \ne v} \{ d_u^{(k-1)} + w(u,v) \} \}, & k \ge 1 \end{cases} \end{align*} $$

$\ell_{ij}^{(m)}$:从$i$$j$经过不超过$m$条边的最短路径的长度

$$ \begin{align*} \quad \ell_{ij}^{(m)} & = \begin{cases} \infty, & m = 0 \\ \min \{ \ell_{ij}^{(m-1)}, ~ \min_{k \ne j} \{ \ell_{ik}^{(m-1)} + w_{kj} \} \}, & m \ge 1 \end{cases} \\ & = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \end{align*} $$

动态规划 实现

$$ \begin{align*} \quad \ell_{ij}^{(m)} = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \end{align*} $$

def sp_all_dp():
    l = np.array(w, copy=True)
    p = init_p()  # 初始化前驱矩阵
    for _ in range(n - 2):
        ll = np.array(l, copy=True)
        for i in range(n):
            for j in range(n):
                for k in range(n):
                    if ll[i, k] + w[k, j] < l[i, j]:
                        l[i, j] = ll[i, k] + w[k, j]  # 更新L
                        p[i, j] = k + 1  # 更新前驱
    return l, p

四重 for 循环时间复杂度$\Theta(n^4)$,二维表格空间复杂度$\Theta(n^2)$

动态规划 例子

$$ \begin{align*} \quad \ell_{ij}^{(m)} & = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \\[5px] \Lv^{(1)} & = \begin{bmatrix} 0 & 3 & 8 & \infty & -4 \\ \infty & 0 & \infty & 1 & 7 \\ \infty & 4 & 0 & \infty & \infty \\ 2 & \infty & -5 & 0 & \infty \\ \infty & \infty & \infty & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(1)} & = \begin{bmatrix} - & 1 & 1 & - & 1 \\ - & - & - & 2 & 2 \\ - & 3 & - & - & - \\ 4 & - & 4 & - & - \\ - & - & - & 5 & - \end{bmatrix} \end{align*} $$

动态规划 例子

$$ \begin{align*} \quad \ell_{ij}^{(m)} & = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \\[5px] \Lv^{(2)} & = \begin{bmatrix} 0 & 3 & 8 & \class{blue}{2} & -4 \\ \class{blue}{3} & 0 & \class{blue}{-4} & 1 & 7 \\ \infty & 4 & 0 & \class{blue}{5} & \class{blue}{11} \\ 2 & \class{blue}{-1} & -5 & 0 & \class{blue}{-2} \\ \class{blue}{8} & \infty & \class{blue}{1} & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(2)} & = \begin{bmatrix} - & 1 & 1 & \class{blue}{5} & 1 \\ \class{blue}{4} & - & \class{blue}{4} & 2 & 2 \\ - & 3 & - & \class{blue}{2} & \class{blue}{2} \\ 4 & \class{blue}{3} & 4 & - & \class{blue}{1} \\ \class{blue}{4} & - & \class{blue}{4} & 5 & - \end{bmatrix} \end{align*} $$

动态规划 例子

$$ \begin{align*} \quad \ell_{ij}^{(m)} & = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \\[5px] \Lv^{(3)} & = \begin{bmatrix} 0 & 3 & \class{blue}{-3} & 2 & -4 \\ 3 & 0 & -4 & 1 & \class{blue}{-1} \\ \class{blue}{7} & 4 & 0 & 5 & 11 \\ 2 & -1 & -5 & 0 & -2 \\ 8 & \class{blue}{5} & 1 & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(3)} & = \begin{bmatrix} - & 1 & \class{blue}{4} & 5 & 1 \\ 4 & - & 4 & 2 & \class{blue}{1} \\ \class{blue}{4} & 3 & - & 2 & 2 \\ 4 & 3 & 4 & - & 1 \\ 4 & \class{blue}{3} & 4 & 5 & - \end{bmatrix} \end{align*} $$

动态规划 例子

$$ \begin{align*} \quad \ell_{ij}^{(m)} & = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \\[5px] \Lv^{(4)} & = \begin{bmatrix} 0 & \class{blue}{1} & -3 & 2 & -4 \\ 3 & 0 & -4 & 1 & -1 \\ 7 & 4 & 0 & 5 & \class{blue}{3} \\ 2 & -1 & -5 & 0 & -2 \\ 8 & 5 & 1 & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(4)} & = \begin{bmatrix} - & \class{blue}{3} & 4 & 5 & 1 \\ 4 & - & 4 & 2 & 1 \\ 4 & 3 & - & 2 & \class{blue}{1} \\ 4 & 3 & 4 & - & 1 \\ 4 & 3 & 4 & 5 & - \end{bmatrix} \end{align*} $$

动态规划 改进

矩阵乘法$\Av \leftarrow \Av \cdot \Bv$ vs. 最短路径$\Lv \leftarrow \Lv \circ \Wv$

for i in range(n):
    for j in range(n):
        for k in range(n):
            a[i,j] = a[i,j] + a[i,k] * b[k,j]

for i in range(n):
    for j in range(n):
        for k in range(n):
            l[i,j] = min(l[i,j], l[i,k] + w[k,j])
  • 变量:$a[]$对应$l[]$$b[]$对应$w[]$
  • 二元运算:高优先级$*$对应$+$、低优先级$+$对应$\min$

二元运算$\circ$也有结合律!

动态规划 改进

注意初始时$\Lv^{(1)} = \Wv$,计算$\Lv^{(n)}$最外层循环迭代$\Theta(n)$

$$ \begin{align*} \quad \Lv^{(1)} & = \Wv \\ \Lv^{(2)} & = \Lv^{(1)} \circ \Wv = \Wv^2 \\ \Lv^{(3)} & = \Lv^{(2)} \circ \Wv = \Wv^3 \\ \Lv^{(4)} & = \Lv^{(3)} \circ \Wv = \Wv^4 \\ \end{align*} $$

利用结合律最外层循环只需$\Theta(\lg n)$轮,总时间复杂度$\Theta(n^3 \lg n)$

$$ \begin{align*} \quad \Lv^{(1)} & = \Wv \\ \Lv^{(2)} & = \Wv^2 = \Lv^{(1)} \circ \Lv^{(1)} \\ \Lv^{(4)} & = \Wv^4 = \Lv^{(2)} \circ \Lv^{(2)} \\ \Lv^{(8)} & = \Wv^8 = \Lv^{(4)} \circ \Lv^{(4)} \end{align*} $$

动态规划 改进

  • $m \ge n-1$$\Lv^{(m)}$不再变化,因此$m$不断乘$2$直至达到$n-1$
  • 前驱矩阵的更新也有变化
def sp_all_dp_plus():
    l = np.array(w, copy=True)
    p = init_p()  # 初始化前驱矩阵
    m = 1
    while m < n - 1:
        ll = np.array(l, copy=True)
        for i in range(n):
            for j in range(n):
                for k in range(n):
                    if ll[i, k] + ll[k, j] < l[i, j]:
                        l[i, j] = ll[i, k] + ll[k, j]  # 更新L
                        p[i, j] = p[k, j]  # 更新前驱
        m *= 2
    return l, p
动态规划 再改进

$\ell_{ij}^{(m)}$:从$i$$j$经过不超过$m$条边的最短路径的长度

$$ \begin{align*} \quad \ell_{ij}^{(m)} = \begin{cases} \infty, & m = 0 \\ \min_k \{ \ell_{ik}^{(m-1)} + w_{kj} \}, & m \ge 1 \end{cases} \end{align*} $$

$d_{ij}^{(k)}$:从$i$$j$中间结点属于集合$\{1,2, \ldots,k\}$的最短路径的长度

  • 若最短路径不经过$k$,则$d_{ij}^{(k)} = d_{ij}^{(k-1)}$
  • 若最短路径经过$k$,不妨设为$i \overset{p_1}{\rightsquigarrow} k \overset{p_2}{\rightsquigarrow} j$,根据最优子结构性,$p_1$$p_2$的长度分别为$d_{ik}^{(k-1)}$$d_{kj}^{(k-1)}$

$$ \begin{align*} \quad d_{ij}^{(k)} = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \end{align*} $$

Floyd-Warshall 实现

$$ \begin{align*} \quad d_{ij}^{(k)} = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \end{align*} $$

def floyd_warshall():
    d = np.array(w, copy=True)
    p = init_p()  # 初始化前驱矩阵
    for k in range(n):
        dd = np.array(d, copy=True)
        for i in range(n):
            for j in range(n):
                if dd[i, k] + dd[k, j] < d[i, j]:
                    d[i, j] = dd[i, k] + dd[k, j]  # 更新d
                    p[i, j] = p[k, j]  # 更新前驱
        print(d)
        print(p)
    return d, p

三重 for 循环时间复杂度$\Theta(n^3)$,二维表格空间复杂度$\Theta(n^2)$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(0)} & = \begin{bmatrix} 0 & 3 & 8 & \infty & -4 \\ \infty & 0 & \infty & 1 & 7 \\ \infty & 4 & 0 & \infty & \infty \\ 2 & \infty & -5 & 0 & \infty \\ \infty & \infty & \infty & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(0)} & = \begin{bmatrix} - & 1 & 1 & - & 1 \\ - & - & - & 2 & 2 \\ - & 3 & - & - & - \\ 4 & - & 4 & - & - \\ - & - & - & 5 & - \end{bmatrix} \end{align*} $$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(1)} & = \begin{bmatrix} 0 & 3 & 8 & \infty & -4 \\ \infty & 0 & \infty & 1 & 7 \\ \infty & 4 & 0 & \infty & \infty \\ 2 & \class{blue}{5} & -5 & 0 & \class{blue}{-2} \\ \infty & \infty & \infty & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(1)} & = \begin{bmatrix} - & 1 & 1 & - & 1 \\ - & - & - & 2 & 2 \\ - & 3 & - & - & - \\ 4 & \class{blue}{1} & 4 & - & \class{blue}{1} \\ - & - & - & 5 & - \end{bmatrix} \end{align*} $$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(2)} & = \begin{bmatrix} 0 & 3 & 8 & \class{blue}{4} & -4 \\ \infty & 0 & \infty & 1 & 7 \\ \infty & 4 & 0 & \class{blue}{5} & \class{blue}{11} \\ 2 & 5 & -5 & 0 & -2 \\ \infty & \infty & \infty & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(2)} & = \begin{bmatrix} - & 1 & 1 & \class{blue}{2} & 1 \\ - & - & - & 2 & 2 \\ - & 3 & - & \class{blue}{2} & \class{blue}{2} \\ 4 & 1 & 4 & - & 1 \\ - & - & - & 5 & - \end{bmatrix} \end{align*} $$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(3)} & = \begin{bmatrix} 0 & 3 & 8 & 4 & -4 \\ \infty & 0 & \infty & 1 & 7 \\ \infty & 4 & 0 & 5 & 11 \\ 2 & \class{blue}{-1} & -5 & 0 & -2 \\ \infty & \infty & \infty & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(3)} & = \begin{bmatrix} - & 1 & 1 & 2 & 1 \\ - & - & - & 2 & 2 \\ - & 3 & - & 2 & 2 \\ 4 & \class{blue}{3} & 4 & - & 1 \\ - & - & - & 5 & - \end{bmatrix} \end{align*} $$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(4)} & = \begin{bmatrix} 0 & 3 & \class{blue}{-1} & 4 & -4 \\ \class{blue}{3} & 0 & \class{blue}{-4} & 1 & \class{blue}{-1} \\ \class{blue}{7} & 4 & 0 & 5 & \class{blue}{3} \\ 2 & -1 & -5 & 0 & -2 \\ \class{blue}{8} & \class{blue}{5} & \class{blue}{1} & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(4)} & = \begin{bmatrix} - & 1 & \class{blue}{4} & 2 & 1 \\ \class{blue}{4} & - & \class{blue}{4} & 2 & \class{blue}{1} \\ \class{blue}{4} & 3 & - & 2 & \class{blue}{1} \\ 4 & 3 & 4 & - & 1 \\ \class{blue}{4} & \class{blue}{3} & \class{blue}{4} & 5 & - \end{bmatrix} \end{align*} $$

Floyd-Warshall 例子

$$ \begin{align*} \quad d_{ij}^{(k)} & = \begin{cases} w_{ij}, & k = 0 \\ \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \}, & k \ge 1 \end{cases} \\[5px] \Dv^{(5)} & = \begin{bmatrix} 0 & \class{blue}{1} & \class{blue}{-3} & \class{blue}{2} & -4 \\ 3 & 0 & -4 & 1 & -1 \\ 7 & 4 & 0 & 5 & 3 \\ 2 & -1 & -5 & 0 & -2 \\ 8 & 5 & 1 & 6 & 0 \end{bmatrix} \\[5px] \Pv^{(5)} & = \begin{bmatrix} - & \class{blue}{3} & \class{blue}{4} & \class{blue}{5} & 1 \\ 4 & - & 4 & 2 & 1 \\ 4 & 3 & - & 2 & 1 \\ 4 & 3 & 4 & - & 1 \\ 4 & 3 & 4 & 5 & - \end{bmatrix} \end{align*} $$

传递闭包

输入:有向图$\Gcal = (\Vcal, \Ecal)$
输出:传递闭包$\Gcal^* = (\Vcal, \Ecal^*)$,其中$\Ecal^* = \{ (i,j) \mid \exists p: i \overset{p}{\rightsquigarrow} j \}$

方法一:给$\Ecal$中每条边赋权重$1$,然后运行 Floyd-Warshall 算法

  • $d_{ij} < n$,则存在一条从$i$$j$的路径
  • $d_{ij} = \infty$,则不存在一条从$i$$j$的路径

方法二:引入矩阵$\Tv = [t_{ij}]_{1 \le i,j \le n}$,其中$t_{ij} = \begin{cases} 1, & \exists p: i \overset{p}{\rightsquigarrow} j \\ 0, & \text{其它} \end{cases}$

$$ \begin{align*} \quad t_{ij}^{(0)} & = \begin{cases} 0, & i \ne j \text{ 且 } (i,j) \not \in \Ecal \\ 1, & i = j \text{ 或 } (i,j) \in \Ecal \end{cases} \\[5px] t_{ij}^{(k)} & = t_{ij}^{(k-1)} \vee (t_{ik}^{(k-1)} \wedge t_{kj}^{(k-1)}) \end{align*} $$

传递闭包 实现

$\min \rightarrow \vee$$+ \rightarrow \wedge$,逻辑运算比算术运算快,空间需求也较小

$$ \begin{align*} \quad d_{ij}^{(k)} & = \min \{ d_{ij}^{(k-1)}, d_{ik}^{(k-1)} + d_{kj}^{(k-1)} \} \\ t_{ij}^{(k)} & = t_{ij}^{(k-1)} \vee (t_{ik}^{(k-1)} \wedge t_{kj}^{(k-1)}) \end{align*} $$

def transitive_closure():
    t = np.zeros((n, n), dtype="int")
    for i in range(n):
        for j in range(n):
            if i == j or w[i, j] == 1:
                t[i, j] = 1
    for k in range(n):
        for i in range(n):
            for j in range(n):
                t[i, j] = t[i, j] | (t[i, k] & t[k, j])  # 更新t
    return t

三重 for 循环时间复杂度$\Theta(n^3)$,二维表格空间复杂度$\Theta(n^2)$

传递闭包 例子

$$ \begin{align*} t_{ij}^{(0)} & = \begin{cases} 0, & i \ne j \text{ 且 } (i,j) \not \in \Ecal \\ 1, & i = j \text{ 或 } (i,j) \in \Ecal \end{cases} \\[10px] t_{ij}^{(k)} & = t_{ij}^{(k-1)} \vee (t_{ik}^{(k-1)} \wedge t_{kj}^{(k-1)}) \\[10px] \Tv^{(0)} & = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 1 & 0 \\ 1 & 0 & 1 & 1 \end{bmatrix}, ~ \Tv^{(1)} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 1 & 0 \\ 1 & 0 & 1 & 1 \end{bmatrix} \\[10px] \Tv^{(2)} & = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 1 & \class{blue}{1} \\ 1 & 0 & 1 & 1 \end{bmatrix}, ~ \Tv^{(3)} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 1 & 1 \\ 1 & \class{blue}{1} & 1 & 1 \end{bmatrix}, ~ \Tv^{(4)} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ \class{blue}{1} & 1 & 1 & 1 \\ \class{blue}{1} & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 \end{bmatrix} \end{align*} $$

g ②->③ ②->④ ③->② ④->③ ④->①

稀疏图 再改进

Floyd-Warshall 算法的时间复杂度是$\Theta(\shu\Vcal\shu^3)$

实现 一般时间复杂度 稠密图 稀疏图
Bellman-Ford $\Theta(\shu\Vcal\shu \shu\Ecal\shu)$ $\Theta(\shu\Vcal\shu^3)$ $O(\shu\Vcal\shu^2)$
Dijkstra 线性数组 $\Theta(\shu\Vcal\shu^2 + \shu\Ecal\shu)$ $\Theta(\shu\Vcal\shu^2)$ $\Theta(\shu\Vcal\shu^2)$
二叉堆 $\Theta((\shu\Vcal\shu + \shu\Ecal\shu) \lg \shu\Vcal\shu)$ $\Theta(\shu\Vcal\shu^2 \lg \shu\Vcal\shu)$ $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu)$
斐波那契堆 $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu + \shu\Ecal\shu)$ $\Theta(\shu\Vcal\shu^2)$ $\Theta(\shu\Vcal\shu \lg \shu\Vcal\shu)$

对于稀疏图,以每个点为源点运行 Dijkstra 算法$\Theta(\shu\Vcal\shu^2 \lg \shu\Vcal\shu)$

有负边怎么办?Dijkstra 算法不能处理有负边的图

重新赋权

构造新的权重函数$\what: \Ecal \mapsto \class{blue}{\Rbb^+}$且满足路径等价性$p$在使用$w$时是最短路径当且仅当其在使用$\what$时也是最短路径

引入函数$h: \Vcal \mapsto \Rbb$,定义$\what(u,v) = w(u,v) + h(u) - h(v)$

$p = \langle v_0, v_1, \ldots, v_k \rangle$$v_0$$v_k$的任意一条路径

$$ \begin{align*} \quad \sum_{i=1}^k \what(v_{i-1}, v_i) = \sum_{i=1}^k w(v_{i-1}, v_i) + h(v_0) - h(v_k) \end{align*} $$

固定$v_0$$v_k$,对任意路径,新旧权重函数引起的变化是常数,不依赖中间经过的点,因此路径等价性满足

$v_0=v_k$易知$p$在使用$w$时是负环当且仅当在使用$\what$时也是负环

重新赋权

构造新的权重函数$\what: \Ecal \mapsto \class{blue}{\Rbb^+}$且满足路径等价性$p$在使用$w$时是最短路径当且仅当其在使用$\what$时也是最短路径

引入函数$h: \Vcal \mapsto \Rbb$,定义$\what(u,v) = w(u,v) + h(u) - h(v)$

如何设计$h$保证$\what$的非负性呢?

$0 \le \what(u,v) = w(u,v) + h(u) - h(v) \Longleftrightarrow h(v) \le h(u) + w(u,v)$

联想到三角不等式$\delta(s,v) \le \delta(s,u) + w(u,v)$,可取$h(v) = \delta(s,v)$

Johnson 算法

引入新结点$0$,并有权重为零的边指向其它所有点

$0$没有入边,不会影响其它点间的最短路径,也不会产生新的环

Johnson 算法

$0$为源点运行 Bellman-Ford 算法

  1. 若检测到负环,则原图有负环,问题无解,否则计算$\delta(0,v)$作为$h(v)$
  2. 对所有边重新赋予权重$\what(u,v) = w(u,v) + h(u) - h(v)$
  3. 在新权重下,以每个点为源点运行 Dijkstra 算法
  4. 求得$u$$v$的最短路径后,加上$h(v)-h(u)$复原出原最短路径长度
Johnson 算法 实现

g = {
    "1": {"2": 3, "3": 8, "5": -4},  # w(1,2) = 3, w(1,3) = 8, w(1,5) = -4
    "2": {"4": 1, "5": 7},           # w(2,4) = 1, w(2,5) = 7
    "3": {"2": 4},                   # w(3,2) = 4
    "4": {"1": 2, "3": -5},          # w(4,1) = 2, w(4,3) = -5
    "5": {"4": 6},                   # w(5,4) = 6
}
g["0"] = {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0}  # 添加新结点0
h = bellman_ford(s="0")  # 以0为源点运行Bellman-Ford算法

for u in g:
    for v in g[u]:
        g[u][v] += h[u] - h[v]  # 根据h重新给边赋权

D = dict()
for u in g:
    if u != "0":
        D[u] = dijkstra(u)  # 以每个结点为源点运行Dijkstra算法
        del D[u]["0"]

del g["0"]

for u in g:
    for v in g:
        D[u][v] += h[v] - h[u]  # 还原原权重下的最短路径长度

print(D)
------------------------------
'1': {'1': 0, '2': 1,  '3': -3, '4': 2, '5': -4}
'2': {'1': 3, '2': 0,  '3': -4, '4': 1, '5': -1}
'3': {'1': 7, '2': 4,  '3': 0,  '4': 5, '5': 3 }
'4': {'1': 2, '2': -1, '3': -5, '4': 0, '5': -2}
'5': {'1': 8, '2': 5,  '3': 1,  '4': 6, '5': 0 }
作业

算法导论 3rd

计算题:24.1-1、24.4-1、25.2-1

设计、证明题:24.1-3、24-3、25.2-7

思考题:25.2-6