Loading docs/graph/shortest-path.md +13 −16 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ for (k = 1; k <= n; k++) { 三角形不等式: $dist(v) \leq dist(u) + edge\_len(u, v)$ 。 证明:反证法,如果不满足,那么可以用 $relax$ 操作来更新 $dist(v)$ 的值。 证明:反证法,如果不满足,那么可以用松弛操作来更新 $dist(v)$ 的值。 Bellman-Ford 算法如下: Loading @@ -114,7 +114,7 @@ Bellman-Ford 算法如下: while (1) for each edge(u, v) relax(u, v); ``` 当一次循环中没有 $relax$ 操作成功时停止。 当一次循环中没有松弛操作成功时停止。 每次循环是 $O(m)$ 的,那么最多会循环多少次呢? Loading @@ -124,11 +124,9 @@ while (1) for each edge(u, v) relax(u, v); 我们考虑最短路存在的时候。 由于一次 $relax$ 会使(被 $relax$ 的)最短路的边数至少 $+1$ ,而最短路的边数最多为 $n-1$ 。 由于一次松弛操作会使最短路的边数至少 $+1$ ,而最短路的边数最多为 $n-1$ 。 所以最多(连续) $relax$ $n-1$ 次……( $relax$ 一定是环环相扣的,不然之前就能被 $relax$ 掉) 所以最多循环 $n-1$ 次。 所以最多执行 $n-1$ 次松弛操作,即最多循环 $n-1$ 次。 总时间复杂度 $O(NM)$ 。 **(对于最短路存在的图)** Loading @@ -152,7 +150,7 @@ for (i = 1; i < n; i++) { 给一张有向图,问是否存在负权环。 做法很简单,跑 Bellman-Ford 算法,如果有个点被 $relax$ 成功了 $n$ 次,那么就一定存在。 做法很简单,跑 Bellman-Ford 算法,如果有个点被松弛成功了 $n$ 次,那么就一定存在。 如果 $n-1$ 次之内算法结束了,就一定不存在。 Loading @@ -160,11 +158,11 @@ for (i = 1; i < n; i++) { 即 Shortest Path Faster Algorithm。 很多时候我们并不需要那么多无用的 $relax$ 操作。 很多时候我们并不需要那么多无用的松弛操作。 很显然,只有上一次被 $relax$ 的结点,所连接的边,才有可能引起下一次的 $relax$ 。 很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。 那么我们用队列来维护“哪些结点可能会引起 $relax$ ”,就能只访问必要的边了。 那么我们用队列来维护“哪些结点可能会引起松弛操作”,就能只访问必要的边了。 ```text q = new queue(); Loading @@ -182,7 +180,7 @@ while (!q.empty()) { } ``` SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ (玄学),但 **理论上界** 为 $O(NM)$ ,精心设计的稠密图可以随便卡掉 SPFA,所以考试时谨慎使用(NOI 2018 卡 SPFA)。 SPFA 在随机图上的期望时间复杂度为 $O(kM)~ (k\approx 2)$ ,但最坏情况下的时间复杂度为 $O(NM)$ ,事实上使 SPFA 跑到时间复杂度上限的图非常容易构造,所以考试时谨慎使用(NOI 2018 卡 SPFA)。 #### SPFA 的优化之 SLF Loading Loading @@ -212,9 +210,8 @@ IPA:/ˈdikstrɑ/或/ˈdɛikstrɑ/。 然后重复这些操作: (1) $relax$ 那些刚刚被加入第一个集合的结点的所有出边。 (2)从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。 1. 对那些刚刚被加入第一个集合的结点的所有出边执行松弛操作。 2. 从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。 直到第二个集合为空,算法结束。 Loading @@ -238,9 +235,9 @@ IPA:/ˈdikstrɑ/或/ˈdɛikstrɑ/。 再证明第一个集合中的元素的最短路已经确定。 第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值, $relax$ 又是加上非负数,所以仍然成立。(归纳)(利用非负权值的性质) 第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值, 每次松弛操作又是加上非负数,所以仍然成立。(归纳)(利用非负权值的性质) 第二步,考虑每次加进来的结点,到他的最短路,上一步必然是第一个集合中的元素(否则他不会是第二个集合中的最小值,而且有第一步的性质),又因为第一个集合已经全部 $relax$ 过了,所以最短路显然确定了。 第二步,考虑每次加进来的结点,到他的最短路,上一步必然是第一个集合中的元素(否则他不会是第二个集合中的最小值,而且有第一步的性质),又因为第一个集合内的点已经全部松弛过了,所以最短路显然确定了。 ```text H = new heap(); Loading Loading
docs/graph/shortest-path.md +13 −16 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ for (k = 1; k <= n; k++) { 三角形不等式: $dist(v) \leq dist(u) + edge\_len(u, v)$ 。 证明:反证法,如果不满足,那么可以用 $relax$ 操作来更新 $dist(v)$ 的值。 证明:反证法,如果不满足,那么可以用松弛操作来更新 $dist(v)$ 的值。 Bellman-Ford 算法如下: Loading @@ -114,7 +114,7 @@ Bellman-Ford 算法如下: while (1) for each edge(u, v) relax(u, v); ``` 当一次循环中没有 $relax$ 操作成功时停止。 当一次循环中没有松弛操作成功时停止。 每次循环是 $O(m)$ 的,那么最多会循环多少次呢? Loading @@ -124,11 +124,9 @@ while (1) for each edge(u, v) relax(u, v); 我们考虑最短路存在的时候。 由于一次 $relax$ 会使(被 $relax$ 的)最短路的边数至少 $+1$ ,而最短路的边数最多为 $n-1$ 。 由于一次松弛操作会使最短路的边数至少 $+1$ ,而最短路的边数最多为 $n-1$ 。 所以最多(连续) $relax$ $n-1$ 次……( $relax$ 一定是环环相扣的,不然之前就能被 $relax$ 掉) 所以最多循环 $n-1$ 次。 所以最多执行 $n-1$ 次松弛操作,即最多循环 $n-1$ 次。 总时间复杂度 $O(NM)$ 。 **(对于最短路存在的图)** Loading @@ -152,7 +150,7 @@ for (i = 1; i < n; i++) { 给一张有向图,问是否存在负权环。 做法很简单,跑 Bellman-Ford 算法,如果有个点被 $relax$ 成功了 $n$ 次,那么就一定存在。 做法很简单,跑 Bellman-Ford 算法,如果有个点被松弛成功了 $n$ 次,那么就一定存在。 如果 $n-1$ 次之内算法结束了,就一定不存在。 Loading @@ -160,11 +158,11 @@ for (i = 1; i < n; i++) { 即 Shortest Path Faster Algorithm。 很多时候我们并不需要那么多无用的 $relax$ 操作。 很多时候我们并不需要那么多无用的松弛操作。 很显然,只有上一次被 $relax$ 的结点,所连接的边,才有可能引起下一次的 $relax$ 。 很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。 那么我们用队列来维护“哪些结点可能会引起 $relax$ ”,就能只访问必要的边了。 那么我们用队列来维护“哪些结点可能会引起松弛操作”,就能只访问必要的边了。 ```text q = new queue(); Loading @@ -182,7 +180,7 @@ while (!q.empty()) { } ``` SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ (玄学),但 **理论上界** 为 $O(NM)$ ,精心设计的稠密图可以随便卡掉 SPFA,所以考试时谨慎使用(NOI 2018 卡 SPFA)。 SPFA 在随机图上的期望时间复杂度为 $O(kM)~ (k\approx 2)$ ,但最坏情况下的时间复杂度为 $O(NM)$ ,事实上使 SPFA 跑到时间复杂度上限的图非常容易构造,所以考试时谨慎使用(NOI 2018 卡 SPFA)。 #### SPFA 的优化之 SLF Loading Loading @@ -212,9 +210,8 @@ IPA:/ˈdikstrɑ/或/ˈdɛikstrɑ/。 然后重复这些操作: (1) $relax$ 那些刚刚被加入第一个集合的结点的所有出边。 (2)从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。 1. 对那些刚刚被加入第一个集合的结点的所有出边执行松弛操作。 2. 从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。 直到第二个集合为空,算法结束。 Loading @@ -238,9 +235,9 @@ IPA:/ˈdikstrɑ/或/ˈdɛikstrɑ/。 再证明第一个集合中的元素的最短路已经确定。 第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值, $relax$ 又是加上非负数,所以仍然成立。(归纳)(利用非负权值的性质) 第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值, 每次松弛操作又是加上非负数,所以仍然成立。(归纳)(利用非负权值的性质) 第二步,考虑每次加进来的结点,到他的最短路,上一步必然是第一个集合中的元素(否则他不会是第二个集合中的最小值,而且有第一步的性质),又因为第一个集合已经全部 $relax$ 过了,所以最短路显然确定了。 第二步,考虑每次加进来的结点,到他的最短路,上一步必然是第一个集合中的元素(否则他不会是第二个集合中的最小值,而且有第一步的性质),又因为第一个集合内的点已经全部松弛过了,所以最短路显然确定了。 ```text H = new heap(); Loading