显然,对于问题:所有结点对的最短路径问题,我们需要找到图中任何两个结点间的最短路径。同时,我们能轻易得对上一章所学的Bellman_Ford或是Dijkstra算法对每个结点都调用一次而得出结果——但显然,将会浪费很多时间。
以Dijkstra算法为例,若实现它的方式为数组实现,那么Extract会搜索整个数组,消耗O(|V|)的时间——加上外层嵌套的while循环,同时处理边所用到的时间为|E|,那么算法运行的总时间为O(|V|² + |E|) = O(|V|²)。而调用 n 次,也就需要O(|V|³)的时间。
至于在图稀疏的情况下,若 采用二叉堆实现:隐藏在Relax操作中的DecreaseKey、Extract_Min均消耗O(lgV)的时间,并且我们仍然只对所有的边进行一次Relax操作,因此共有|E|次DecreaseKey,|V|次Extract_Min操作;同时对原来以数组形式表示的图进行Build_Min_Heap操作会消耗O(n)时间——虽然看上去为O(nlgn)次,不过若我们更为精确地计算:每一层高度为 h 的结点,它的Max_Heapify操作并不完全等于堆的高度,而是等于它本身的高度h。这样一来,计算所有有儿子的结点处理时间的∑运算便能得出O(n)的结果。(其中高度h层的结点数目为:
,式中 n 是高度的函数,而非总元素数目,同样设最底下一层为0层)
详细的证明见书P88,在此我们仍需要把重点放在图中。
经过上述的计算,我们得到了总运行时间O((V+E)lgV)的结果,同时,至于为什么能够在从源节点出发的条件下达到O(ElgV)的结果,我目前还不清楚。猜测可能是由于当从源节点出发能够到达所有结点时,在处理完毕源节点后,对于后面的结点,在Max_Heapify中,将A[A.heapsize]换到堆的顶部时,只下渗与高度有关的次数:即下渗到同样d值为∞的层数便停止——因此,其与以上关于Build_Min_Heap的证明类似,对整体摊还分析产生的结果为O(V),即O(V+ElgV) = O(ElgV)。同时,在的条件下,得到有O(V²)的结果。
对于我们来说,从直觉上来看,ElgV是小于V²的——但请不要忽略,E做到的是连接图中的顶点,因此它应该比V大的多——甚至在最大的情况下,会达到V!的水平。
以上是通过二叉堆进行优化,不过在此之后,还能通过斐波那契堆进行优化——由于extract_min的摊还时间为θ(lgn),同时DecreaseKey甚至达到了Θ(1)的时间。(这个堆的实现不难,不过时间摊还分析有一定的难度)因此使用它将会使整个算法运行的时间到达O(