使用 decrease-key 而不是重新插入节点的原因是为了保持优先队列中节点数量的少,从而保持总优先队列出队操作的少和每个优先队列平衡的成本低。
在 Dijkstra 算法的实现中,如果将节点与它们的新优先级重新插入到优先队列中,则对于图中的每个 m 条边都会添加一个节点到优先队列中。这意味着在优先队列上有 m 个入队操作和 m 个出队操作,给出了总运行时间 O(m T
e + m T
d),其中 T
e 是将元素入队到优先队列所需的时间,T
d 是从优先队列中取出元素所需的时间。
在支持降低关键字的Dijkstra算法实现中,持有节点的优先队列起始时有n个节点,并且在算法的每个步骤中移除一个节点。这意味着堆出队的总数是n。每个节点可能会被调用一次或多次降低关键字,导致降低关键字的总数最多为m。这给出了运行时间(n T
e + n T
d + m T
k),其中T
k是调用降低关键字所需的时间。
那么这对运行时间有什么影响呢?这取决于您使用的优先队列。以下是一个快速表格,展示了不同优先队列和不同Dijkstra算法实现的总运行时间:
Queue | T_e | T_d | T_k | w/o Dec-Key | w/Dec-Key
---------------+--------+--------+--------+-------------+---------------
Binary Heap |O(log N)|O(log N)|O(log N)| O(M log N) | O(M log N)
Binomial Heap |O(log N)|O(log N)|O(log N)| O(M log N) | O(M log N)
Fibonacci Heap | O(1) |O(log N)| O(1) | O(M log N) | O(M + N log N)
从大多数优先队列的类型来看,它们在渐近运行时间上并没有什么区别,并且使用 decrease-key 版本也不太可能有更好的表现。然而,如果您使用 Fibonacci 堆 实现优先队列,那么使用 decrease-key 时 Dijkstra 算法将在渐近意义下更加高效。
简而言之,使用 decrease-key 和一个好的优先队列可以将 Dijkstra 的渐近运行时间降低到比继续进行入队和出队更低的水平。
除此之外,一些更高级的算法,如 Gabow 的最短路径算法,将 Dijkstra 算法作为子程序并且严重依赖于 decrease-key 的实现。它们利用了事先知道有效距离范围的事实,可以基于这个事实构建超级高效的优先队列。
希望这能帮到您!