Dijkstra 算法运行时的区别:优先队列与双向链表
Posted
技术标签:
【中文标题】Dijkstra 算法运行时的区别:优先队列与双向链表【英文标题】:Difference between the runtime of Dijkstra's Algorithm: Priority Queue vs. Doubly Linked List 【发布时间】:2016-03-20 12:02:44 【问题描述】:关于运行时复杂性,以下两者之间有什么区别以及为什么?:
(1) DIJKSTRA 的算法使用常规的优先队列(Heap)
(2) DIJKSTRA 使用双向链表的算法
(除非没有区别)
【问题讨论】:
en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Running_time 不同意“离题”关闭;显然,这是热门话题,正如 *** 定义的大量相关标签所证明的那样,这些标签与问题相关。 【参考方案1】:Dijkstra 算法的最通用版本假定您可以访问某种支持以下操作的优先级队列结构:
make-heap(s, n):在初始距离∞处构建一个包含n个节点的堆,起始节点s除外,它的距离为0。 dequeue-min():移除并返回优先级最低的元素。 decrease-key(obj, key):给定优先级队列中现有的对象obj,将其优先级降低到key给定的级别。Dijkstra 算法需要一次调用 make-heap,O(n) 调用 dequeue-min,O(m) 调用 reduce-key,其中 n 是节点数,m 是边数。总体运行时间实际上可以给出为 O(Tmh + nTdeq + mTdk),其中 Tmh、Tdeq 和 Tdk 分别是进行 make-heap、dequeue 和 reduction-key 的平均(摊销)成本。
现在,假设您的优先级队列是一个双向链表。实际上,有几种方法可以将双向链表用作优先级队列:可以使节点按距离排序,或者可以使它们保持未排序的顺序。让我们逐一考虑。
在已排序的双向链表中,生成堆的成本为 O(n):只需插入起始节点,然后插入距离无穷远的 n - 1 个其他节点。执行 dequeue-min 的成本是 O(1):只需删除第一个元素。然而,做一个减少键的成本是 O(n),因为如果你需要改变一个节点的优先级,你可能不得不移动它,而且你找不到移动它的位置(在最坏的情况下)对节点进行线性扫描。这意味着运行时间将为 O(n + n + nm) = O(mn)。
在一个未排序的双向链表中,做一个 make-heap 的成本仍然是 O(n),因为你需要创建 n 个不同的节点。 dequeue-min 的成本现在是 O(n),因为您必须对列表中的所有节点进行线性扫描才能找到最小值。但是,减少键的成本现在是 O(1),因为您可以就地更新节点的键。这意味着运行时间是 O(n + n2 + m) = O(n2 + m) = O(n2) ,因为边的数量永远不会超过 O(n2)。这是比以前的改进。
对于二叉堆,如果使用标准的线性时间堆化算法,则生成堆的成本为 O(n)。执行出队的成本是 O(log n),执行减少键的成本也是 O(log n)(只需将元素冒泡,直到它位于正确的位置)。这意味着使用二叉堆的 Dijkstra 算法的运行时间是 O(n + n log n + m log n) = O(m log n),因为如果图是连通的,我们将得到 m ≥ n。
在渐近意义上,您可以使用斐波那契堆做得更好。这是一个专门为使 Dijkstra 算法快速而发明的优先级队列。它可以在 O(n) 时间内完成一个 make-heap,在 O(log n) 时间内完成一个 dequeue-min,在(摊销的)O(1) 时间内完成一个减少键。这使得 Dijkstra 算法的运行时间为 O(n + n log n + m) = O(m + n log n),尽管在实践中常数因子使斐波那契堆比二进制堆慢。
所以你有它!不同的优先级队列确实有所作为。有趣的是,“Dijkstra 算法”更像是一个算法家族而不是单个算法,因为数据结构的选择对于算法的快速运行至关重要。
【讨论】:
作为脚注,我认为重要的是要补充一点,二进制堆实现并不总是更快,因为 m 可能等于 n^2 在这种情况下复杂度是 O(n^2 log n)跨度>以上是关于Dijkstra 算法运行时的区别:优先队列与双向链表的主要内容,如果未能解决你的问题,请参考以下文章