【问题标题】:Priority Queue with O(1) Dequeue and O(whatever) Enqueue具有 O(1) Dequeue 和 O(whatever) Enqueue 的优先级队列
【发布时间】:2012-05-28 11:12:05
【问题描述】:

我正在用 C++ 编写一个应用程序,其中对优先级队列进行 O(1) 出队操作至关重要,而入队的复杂性并不那么重要(除非它变成 n^2 或 2^n当然)。

起初我使用的是链表。它非常适合出队 (O(1)),并且具有良好的入队复杂性。唯一的问题是,对其进行排序。并不是说使用具有 O(n) 复杂度的插入排序就可以满足我的需求。但是对链表进行排序是一件很痛苦的事情。太慢了。

矢量一点都不好。出队将是 O(n) 将所有元素移回一个位置。入队仍然是 O(n),但要快得多。

你能推荐更高效的方法吗?谢谢。

【问题讨论】:

  • 在你说的问题中,“Enqueue 的复杂性并不那么重要(当然,除非它变成 n^2 或 2^n)”。然后在 cmets 中,您说重新分配向量(即O(n))太慢了。下定决心:-)
  • 我没有得到两件事:(1)使用归并排序对链表进行排序非常容易。 (2) 如果您将所有对象排入列表,则无需对其进行排序,因为它已经排序。简单插入正确位置的链表已经有 O(1) 出队和 O(n) 入队。不需要排序。奇怪。
  • @SteveJessop 对我来说,出队操作更重要。但是我不希望 Enqueue 成为瓶颈。这将是一个非常大的软件中的一部分。该列表可能包含数百个元素...
  • @antti.huima 我正在使用插入排序,因为它对于已经排序的列表比其他排序更有效。当然,将元素放在正确的位置会更有效。我只是没有坚持 :(
  • @Alfa:请注意,从长远来看,每个元素添加一次,最多删除一次。所以堆提供了最好的整体性能,因为这两个操作都可以是O(log n)。除非您更详细地了解使删除速度比这更快的特定要求是什么,否则您将无法获得最佳建议。大概是为了使卸妆器对 I/O 或 GUI 或其他东西更具响应性。与堆相比,您希望放弃整体性能以加快删除速度。但你并不是说总体上应该慢多少。

标签: c++ algorithm priority-queue


【解决方案1】:

反向排序的vector 有 O(1) pop_back 和 O(n) 插入。

【讨论】:

  • 嗯,这很好,我不敢相信我以前没有这样做过。但是,这需要在向量满时重新分配向量。还是太慢了抱歉。
  • @AlfaOmega08:我没有时间证明这一点,但我怀疑向量的周期性重新分配可能比列表完成的所有小分配更快。
  • @larsmans:无论如何,如果重新分配是vector 的唯一问题,那么您可以尝试使用deque
【解决方案2】:

您可以将队列存储为排序的链表。去掉前面的元素是O(1),在正确的位置插入一个元素是O(n)

但是对链表进行排序是一件很痛苦的事情。太慢了。

您不必在每次插入后执行完整排序。您所要做的就是遍历(已经排序的)列表以找到新元素的正确位置,并将其插入那里。遍历是O(n),插入是O(1)

【讨论】:

  • 是的,我想我的排序方法不是那么聪明...知道它已经排序会有所作为。这是目前的赢家:)
  • 这是正确的答案,但我不能让自己赞成它,因为它同时完全微不足道。
  • 我给出了一个 O(1) delete-min [pop] 和 O(log n) insert [push] 的解决方案。考虑接受我的回答。
【解决方案3】:

如果你愿意从文献中实现,那么我有一个更好的解决方案给你。

最坏情况的复杂性

删除:O(1)

删除最小值:O(1)

求最小值:O(1)

插入:O(log n)

参考

IF MELD 允许采用线性时间,通过使用 Dietz 和 Raman @987654327 的手指搜索树,可以在最坏情况下支持 DELETE-MIN @。 通过使用他们的数据结构 MAKEQUEUE, FINDMIN, DELETEMIN, DELETE 可以在最坏情况时间 O(1 ), INSERT 在最坏情况时间 O(log n) 和 MELD 在最坏情况时间 O(n)。

Brodal, Gerth Stølting. ‘Fast Meldable Priority Queues’. In Proceedings of the 4th International Workshop on Algorithms and Data Structures, 282–290. WADS ’95. London, UK, UK: Springer-Verlag, 1995.

[3]:Dietz, Paul F, and Rajeev Raman. ‘A Constant Update Time Finger Search Tree’. Information Processing Letters 52, no. 3 (1994): 147 – 154.

虽然这使用了RAMmodel of computation

我们的数据结构使用具有单位成本度量和对数字长的随机存取机 (RAM) 模型;

最近,Pointer-Machine 计算模型中的解决方案已给出[1]。这有 O(1) get-min、extract-min、get-max、extract-max 和 O(log n) 插入。

[1]:Brodal, Gerth Stølting, George Lagogiannis, Christos Makris, Athanasios Tsakalidis, and Kostas Tsichlas. ‘Optimal Finger Search Trees in the Pointer Machine’. J. Comput. Syst. Sci. 67, no. 2 (September 2003): 381–418.

【讨论】:

    【解决方案4】:

    Boost 现在包括Boost.Heap,这是一个堆数据结构库,也支持优先级队列操作。 This page 有一个表,其中包含每个提供的数据结构的核心操作的摊销复杂性。斐波那契堆的特征:O(1) 推送,O(log(N)) 弹出,O(1) 增加,以及(如果需要)O(log(N)) 减少。

    【讨论】:

    • 他们都没有O(1) pop,问题声称这是“关键”。当然,有时人们会犯错误,并认为他们需要O(1),而事实上O(log n) 很好。在实践中log n < 64,所以O(log n) 是一个缓慢的O(1)
    • @SteveJessop:是的,这是真的。我知道 OP 说 O(1) pop,但也许 OP 请求优先级队列是有原因的。
    • “优先队列”描述了提问者想要的功能,而不是特定类型的数据结构。底层数据结构(堆或其他),以及由此产生的复杂性是一个重要的实现细节,但它们不影响结果是否是优先级队列。如果提问者确实需要一个带有O(1)pop 的优先级队列,那么通常的堆被排除在外,当然推送的性能也会因此受到影响。
    • 优先队列!=堆。前者是一种 ADT,它提供某些操作,但没有说明它们应该如何实现。
    【解决方案5】:

    可以将平衡二叉搜索树与链表结合起来。树的每个元素都有到它的子元素的链接,并且还链接到下一个前一个元素。然后你可以:

    O(lg n) 插入、删除、搜索; O(1) - 提取最小值+最大值

    如果您不介意使用随机结构,另一种可能是使用跳过列表。比你还拥有:

    O(lg n) 插入、删除、搜索; O(1) - 提取最小值+最大值

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-03
      • 1970-01-01
      • 2016-12-14
      • 1970-01-01
      • 2023-04-02
      相关资源
      最近更新 更多