图论:最短路径问题

14 min

Dijkstra 算法

Dijkstra 算法

  • 前提:图中没有负权边 (non-negative weights)
  • 可以解决单源最短路径问题,即求解从起始点到其他点的最短距离和对应路径
  • 基于性质:一旦某个点的最短路径被确定,它就不会再变小
  • 初始化:对于所有非起点的节点,将其 dist 设为 ++\infty. 对起点设为 00.
  • 贪心选择:
    • 每一步从所有尚未确定最短距离的节点中,选择当前距离起点最近的节点,将其最短距离确定;
    • 然后用该节点去松弛(relax)其邻居的距离(即尝试更新最短距离)。
    • 重复该过程,直到所有可达节点的最短距离被确定。
  • 时间复杂度为 O(V2)O(V^2),可以通过最小堆优化到 O(ElogV)O(E \log V)

例子:

DijkstraExample
DijkstraExample

距离矩阵更新:

steppivotABCDEFGH001A0812C081323E0813234D06132365F061323646H061323547G061323548B06132354\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & B & C & D & E & F & G & H \\ \hline 0 & - & 0 & \infty & \infty & \infty & \infty & \infty & \infty & \infty \\ 1 & A & \textcolor{red}{0} & 8 & 1 & \infty & \infty & \infty & \infty & \infty \\ 2 & C & 0 & 8 & \textcolor{red}{1} & 3 & 2 & \infty & \infty & \infty \\ 3 & E & 0 & 8 & 1 & 3 & \textcolor{red}{2} & 3 & \infty & \infty \\ 4 & D & 0 & 6 & 1 & \textcolor{red}{3} & 2 & 3 & 6 & \infty \\ 5 & F & 0 & 6 & 1 & 3 & 2 & \textcolor{red}{3} & 6 & 4 \\ 6 & H & 0 & 6 & 1 & 3 & 2 & 3 & 5 & \textcolor{red}{4} \\ 7 & G & 0 & 6 & 1 & 3 & 2 & 3 & \textcolor{red}{5} & 4 \\ 8 & B & 0 & \textcolor{red}{6} & 1 & 3 & 2 & 3 & 5 & 4 \\ \end{array}

前驱矩阵更新(用于求解最短距离对应路径):

steppivotABCDEFGH01AAA2CAACC3EAACCE4DDACCED5FDACCEDF6HDACCEHF7GDACCEHF8BDACCEHF\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & B & C & D & E & F & G & H \\ \hline 0 & - & - & - & - & - & - & - & - & - \\ 1 & A & - & A & A & - & - & - & - & - \\ 2 & C & - & A & A & C & C & - & - & - \\ 3 & E & - & A & A & C & C & E & - & - \\ 4 & D & - & D & A & C & C & E & D & - \\ 5 & F & - & D & A & C & C & E & D & F \\ 6 & H & - & D & A & C & C & E & H & F \\ 7 & G & - & D & A & C & C & E & H & F \\ 8 & B & - & D & A & C & C & E & H & F \\ \end{array}

DAG 最短路径

Bellman 算法

  • 前提:有向无环图(DAG)
  • 可以解决单源最短路径问题,即求解从起始点到其他点的最短距离和对应路径
  • 基于性质:每个点的最短距离等于所有【前驱点的最短距离+当前边权】之和的最小值。由于 DAG 中没有环,因此一定存在一个拓扑序使得对于计算点 vv 时,所有其可能的前驱 uu 都被计算完成。
  • 先进行拓扑排序:不断选择入度为 0 的点,删除其出边,更新其他点入度,直到所有点完成排序
  • 按照拓扑排序遍历:
    • 初始化起点距离 dist[s]=0\text{dist}[s]=0 和所有其他点 dist[v]=+\text{dist}[v] = +\infty
    • 对于所有点 uVu \in V:对所有从 uu 出发的边 uvu \to v 执行松弛(relax)1dist[v]=min(dist[v],dist[u]+w(u,v))\text{dist}[v]= \min(\text{dist}[v], \text{dist}[u] + w(u, v))
  • 时间复杂度: O(V+E)O(V+E),因为每个点和每条边都只被处理一次

根据图,可以得到拓扑排序过程(当入度为 0 的点数量不为 1 时,按照字母排序优先处理较小排序的节点):

step当前入度为 0 的点删除的边更新入度
1AAAC, AD, ABA\to C,\ A\to D,\ A\to BC:10, D:21, B:32C:1\to0,\ D:2\to1,\ B:3\to2
2CCCE, CFC\to E,\ C\to FE:10, F:21E:1\to0,\ F:2\to1
3EEED, EFE\to D,\ E\to FD:10, F:10D:1\to0,\ F:1\to0
4DDDG, DBD\to G,\ D\to BG:21, B:21G:2\to1,\ B:2\to1
5FFFHF\to HH:10H:1\to0
6HHHGH\to GG:10G:1\to0
7GGGBG\to BB:10B:1\to0
8BB

最终得到一个合法拓扑序:

A, C, E, D, F, H, G, BA,\ C,\ E,\ D,\ F,\ H,\ G,\ B

因此从源点 AA 出发的距离矩阵和前序矩阵分别为:

steppivotACEDFHGB001A047102C04279103E04235104D04235575F042354576H042354177G042354138B04235413\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & C & E & D & F & H & G & B \\ \hline 0 & - & 0 & \infty & \infty & \infty & \infty & \infty & \infty & \infty \\ 1 & A & \textcolor{red}{0} & 4 & \infty & 7 & \infty & \infty & \infty & 10 \\ 2 & C & 0 & \textcolor{red}{4} & 2 & 7 & 9 & \infty & \infty & 10 \\ 3 & E & 0 & 4 & \textcolor{red}{2} & 3 & 5 & \infty & \infty & 10 \\ 4 & D & 0 & 4 & 2 & \textcolor{red}{3} & 5 & \infty & 5 & 7 \\ 5 & F & 0 & 4 & 2 & 3 & \textcolor{red}{5} & 4 & 5 & 7 \\ 6 & H & 0 & 4 & 2 & 3 & 5 & \textcolor{red}{4} & 1 & 7 \\ 7 & G & 0 & 4 & 2 & 3 & 5 & 4 & \textcolor{red}{1} & 3 \\ 8 & B & 0 & 4 & 2 & 3 & 5 & 4 & 1 & \textcolor{red}{3} \\ \end{array} steppivotACEDFHGB01AAAA2CACACA3EACEEA4DACEEDD5FACEEFDD6HACEEFHD7GACEEFHG8BACEEFHG\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & C & E & D & F & H & G & B \\ \hline 0 & - & - & - & - & - & - & - & - & - \\ 1 & A & - & A & - & A & - & - & - & A \\ 2 & C & - & A & C & A & C & - & - & A \\ 3 & E & - & A & C & E & E & - & - & A \\ 4 & D & - & A & C & E & E & - & D & D \\ 5 & F & - & A & C & E & E & F & D & D \\ 6 & H & - & A & C & E & E & F & H & D \\ 7 & G & - & A & C & E & E & F & H & G \\ 8 & B & - & A & C & E & E & F & H & G \\ \end{array}

需要注意的是如果想知道 DAG 的非源点(以 EE 为源点为例)到其他点的距离,不修改拓扑序,直接从拓扑序里 EE 为起点开始遍历,生成的的距离矩阵和前序矩阵分别为:

steppivotACEDFHGB001A02C03E0134D013355F0132356H0132157G0132118B013211\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & C & E & D & F & H & G & B \\ \hline 0 & - & \infty & \infty & 0 & \infty & \infty & \infty & \infty & \infty \\ 1 & A & \infty & \infty & 0 & \infty & \infty & \infty & \infty & \infty \\ 2 & C & \infty & \infty & 0 & \infty & \infty & \infty & \infty & \infty \\ 3 & E & \infty & \infty & \textcolor{red}{0} & 1 & 3 & \infty & \infty & \infty \\ 4 & D & \infty & \infty & 0 & \textcolor{red}{1} & 3 & \infty & 3 & 5 \\ 5 & F & \infty & \infty & 0 & 1 & \textcolor{red}{3} & 2 & 3 & 5 \\ 6 & H & \infty & \infty & 0 & 1 & 3 & \textcolor{red}{2} & -1 & 5 \\ 7 & G & \infty & \infty & 0 & 1 & 3 & 2 & \textcolor{red}{-1} & 1 \\ 8 & B & \infty & \infty & 0 & 1 & 3 & 2 & -1 & \textcolor{red}{1} \\ \end{array} steppivotACEDFHGB01A2C3EEE4DEEDD5FEEFDD6HEEFHD7GEEFHG8BEEFHG\begin{array}{c|c|cccccccc} \text{step} & \text{pivot} & A & C & E & D & F & H & G & B \\ \hline 0 & - & - & - & - & - & - & - & - & - \\ 1 & A & - & - & - & - & - & - & - & - \\ 2 & C & - & - & - & - & - & - & - & - \\ 3 & E & - & - & - & E & E & - & - & - \\ 4 & D & - & - & - & E & E & - & D & D \\ 5 & F & - & - & - & E & E & F & D & D \\ 6 & H & - & - & - & E & E & F & H & D \\ 7 & G & - & - & - & E & E & F & H & G \\ 8 & B & - & - & - & E & E & F & H & G \\ \end{array}

Roy-Warshall-Floyd 算法

Roy-Warshall-Floyd 算法,也常称为 Floyd-Warshall 算法。

  • 前提:图中允许存在负权边(negative edge),但不能存在负权环(negative cycle)

  • 可以解决所有点对最短路径问题(All-Pairs Shortest Paths, APSP),即同时求解任意两个点之间的最短距离

  • 基于动态规划递推:

    • 子问题:在只允许 1,,k1,\dots,k 作为中间点的限制下,从 iijj 的最短距离是多少。
    • 递推:之前已求解出来在 1,,k11,\dots,k-1 作为中间点的限制下从 iijj 的最短距离,基于此结果计算在 1,,k1,k1,\dots,k-1, \textcolor{red}{k} 作为中间点的限制下从 iijj 的最短距离
    • 形式化表述:设 dij(k)d_{ij}^{(k)} 表示从 iijj 的路径中,只允许使用 {1,,k}\{1,\dots,k\} 作为中间点时的最短距离。当新允许点 kk 作为中间点时,最短路径只有两种可能:一种是不经过 kk,此时距离仍为 dij(k1)d_{ij}^{(k-1)};另一种是经过 kk,此时路径可以拆成 iki\to kkjk\to j 两段,距离为 dik(k1)+dkj(k1)d_{ik}^{(k-1)}+d_{kj}^{(k-1)}。因此逐步放宽允许作为中间点的集合,有递推关系:
    dij(k)=min(dij(k1),dik(k1)+dkj(k1))d_{ij}^{(k)}=\min\left(d_{ij}^{(k-1)},d_{ik}^{(k-1)} + d_{kj}^{(k-1)}\right)
  • 初始化:若 i=ji=j,则 d(i,j)=0d(i,j)=0. 若存在边 iji\to j,则 d(i,j)=w(i,j)d(i,j)=w(i,j). 若不存在边,则初始化为 ++\infty

  • 动态规划过程:依次枚举每个点 kk,尝试让所有路径都允许经过 kk,并且对所有点对 (i,j)(i,j) 更新

    d(i,j)=min(d(i,j),d(i,k)+d(k,j))d(i,j) = \min \left( d(i,j), d(i,k)+d(k,j) \right)
  • 时间复杂度:

    O(V3)O(V^3)

    因为需要三重循环枚举:中间点 kk、起点 ii 和终点 jj

初始化距离矩阵:不允许经过任何中间点

D(0)=ABCDA038B02C04D10D^{(0)} = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & 0 & 3 & \infty & 8 \\ B & \infty & 0 & 2 & \infty \\ C & \infty & \infty & 0 & 4 \\ D & \infty & 1 & \infty & 0 \\ \end{array}

第一步:允许经过点 AA。由于没有任何点能够到达 AA,因此通过 AA 无法产生更短路径。所以矩阵保持不变:

D(1)=D(0)=ABCDA038B02C04D10D^{(1)} = D^{(0)} = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & 0 & 3 & \infty & 8 \\ B & \infty & 0 & 2 & \infty \\ C & \infty & \infty & 0 & 4 \\ D & \infty & 1 & \infty & 0 \\ \end{array}

第二步:允许经过点 BB。现在允许路径:(i,j)V2,  iBj\forall (i,j) \in V^2, \; i\to B\to j. 因为根据 D(1)D^{(1)} 图里有 {a,d}b{c}\{a, d\} \to b \to \{c\},因此需要更新 aca\to cdcd\to c。更新后:

D(2)=ABCDA0358B02C04D130D^{(2)} = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & 0 & 3& \textcolor{red}{5} & 8 \\ B & \infty & 0 & 2 & \infty \\ C & \infty & \infty & 0 & 4 \\ D & \infty & 1 & \textcolor{red}{3} & 0 \\ \end{array}

第三步:允许经过点 CC。现在允许路径:(i,j)V2,  iCj\forall (i,j) \in V^2, \; i\to C\to j. 因为根据 D(2)D^{(2)}2 里有 {a,b,d}c{d}\{a,b,d\}\to c\to \{d\},因此需要更新 ada\to d, bdb\to d.

D(3)=ABCDA0358B026C04D130D^{(3)} = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & 0 & 3 & 5 & \textcolor{red}{8} \\ B & \infty & 0 & 2 & \textcolor{red}{6} \\ C & \infty & \infty & 0 & 4 \\ D & \infty & 1 & 3 & 0 \\ \end{array}

第四步:允许经过点 DD。现在允许路径:(i,j)V2,  iDj\forall (i,j) \in V^2, \; i\to D\to j. 因为根据 D(2)D^{(2)} 图里有 {a,b,c}d{b,c}\{a,b,c\}\to d \to \{b,c\},因此需要更新 aba\to b, aca\to c, bcb\to c, cbc\to b.

最终矩阵:

D(4)=ABCDA0358B026C504D130D^{(4)} = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & 0 & \textcolor{red}{3} & \textcolor{red}{5} & 8 \\ B & \infty & 0 & \textcolor{red}{2} & 6 \\ C & \infty & \textcolor{red}{5} & 0 & 4\\ D & \infty & 1 & 3 & 0 \\ \end{array}

最终最短距离矩阵为 D=D(4)D=D^{(4)}.

前驱矩阵为:

P=ABCDAABABBCCDCDDBP = \begin{array}{c|rrrr} & A & B & C & D \\ \hline A & - & A & B & A \\ B & - & - & B & C \\ C & - & D & - & C \\ D & - & D & B & - \\ \end{array}