Dijkstra算法 —— 计算非负权值的单源最短路径
算法思想
基于贪心策略,每次都选择与源点 S 距离最近的且尚未确认最短路径的宿点 D,认为当前 S-D 的距离就是最终 S-D 的最短路径,因为 S 到其它点的距离都大于 S-D,所以 S 经过其它点再到达 D 点的路径必然更加大于 S-D,因此,当前 S-D 则为 S 到 D的最短路径。注意,此时的 S-D 并不一定是SD边的长度,很有可能是途径了其它一些已经确认了最短路径的点的长度。确定了 S 到 D 的最短路径之后,要更新 S 到其它尚未确定的点如点 E 的距离,判断经过 S-D-E 是否小于 S-E,若小于,则更新 S-E 的值。全部更新完后,继续寻找距离 S 最近的且尚未确认的宿点 D',同点 D 一样,此时 S-D' 就是 S 到 D' 的最短距离,然后继续更新剩余的宿点,直到全部确认最短路径。
该算法的要点在于每次确定了一个最短路径的点,都要更新一遍源点到其它未确认的点的距离,以此来保证下次找到的路径最短的点的路径就是最短路径,这也是该贪心策略成功的关键。不过该算法不能处理带负权值的图,贪心得不到最优解。
Bellman-ford算法 —— 可计算带负权值的单源最短路径
算法思想
初始时,将源点 S 到其它点的距离 D(x) 设为无穷,然后针对图中每一条边 <u, v> ,进行如下松弛处理:D(v) = min ( D(v), D(u) + uv ) 。上述的遍历操作 最多进行 n - 1 次,就可以得到所有点的最短路径。之后再进行一次遍历,若再出现 D(v) > D(u) + uv 情况,那只能说明图中存在负权值环路,无法找到最短路径,只会越松弛越短。
至于为什么最多进行 n - 1 次松弛遍历操作就可以确定 S 到达其它 n - 1 个点的最短路径呢,因为每一次遍历完成,至少会得到一个点的最短路径,原因如下(懒得画图,脑中思考一下就好):
假设 S 的可直达相邻节点有 A B C 三点(也可以更多,不过原理一样),显然 SA , SB, SC 至少有一个是最短路径,那么在一次松弛之后,D(A),D(B),D(C)至少有一个是最短路径,假设D(A)为最短路径,即为 SA,接下来分两种情况,
(1)S 到 B C 两点的最短路径全都需要经过 A 点,那么与 A 直接相邻的节点 D E F 的最短路径肯定要经过 A 点,此时类比 S 和 ABC 三点,SAD, SAE, SAF 也至少有一个是最短路径,因此第二次松弛后,至少又能确定一个点,假设为 D。D 点有可能在第一次松弛时就已经确定了,这是由边的遍历顺序决定的。
(2)S 到 B或 C 的最短路径不需要经过 A ,那么第一次遍历时就已经确定了 A 和 B C 中的某一点。
因此每次松弛至少可以确定一个点的最短路径,且只有 S,A,D.....一直类似情况(1)时才最多需要 n - 1 次松弛。
Floyd算法 —— 计算图中任意两点之间的最短路径
算法思想
该算法基于动态规划思想,对于任意两点 m, n,m 到 n 的最短路径可能经过 1,2,3,4 ...中任意个点,因此首先计算可以经过第一个点时,m n 的最短路径是多少:dp[m][n] = min ( dp[m][1] + dp[1][n], dp[m][n] ),当计算完任意两个点后,再判断可以经过前两个点时,m n 的最短路径:dp[m][n] = min ( dp[m][2] + dp[2][n], dp[m][n] ),若不经过第二个点,那么dp[m][n]不变,若最短路径需要经过第二个点,那么dp[m][n] = dp[m][2] + dp[2][n],至于此时经不经过第一个点,先经过还是后经过根本无需考虑,因为dp[m][2] 和 dp[2][n] 代表的就是考虑过第一个点的情况下的最短路径,在第一次只考虑第一个点时已经计算过了。下面继续计算可以经过前3个点、4个点......直到n个点的情况。
动态规划的最优子结构在于:在可以经过前 k-1 个点的情况下,已知任意两点 m n 的最短路径,那么在可以经过前 k 个点时,m n 的最短路径可以通过:
dp[m][n] = min ( dp[m][k] + dp[k][n], dp[m][n] )