【问题标题】:Difference between std::set and std::priority_queuestd::set 和 std::priority_queue 之间的区别
【发布时间】:2012-04-25 21:03:17
【问题描述】:

由于std::priority_queuestd::set(和std::multiset)都是存储元素并允许您以有序方式访问它们的数据容器,并且具有相同的插入复杂性O(log n),使用一个比另一个(或者,什么样的情况需要一个或另一个?)?

虽然我知道底层结构不同,但我对它们的实现差异并不感兴趣,而是比较它们的性能适用性各种用途。

注意:我知道集合中的无重复项。这就是为什么我还提到了std::multiset,因为它具有与std::set 完全相同的行为,但可以在允许将存储的数据作为相等元素进行比较的情况下使用。所以请不要评论单/多键问题。

【问题讨论】:

  • 优先级队列仅提供对最大元素的访问,而集合则为您提供所有元素的完整排序。这个较弱的接口意味着实现可能更高效(例如,您可以将实际队列数据存储在 vector 中,由于其内存局部性,它可能具有更好的性能)。
  • @KerrekSB 最详尽的答案实际上是评论:D 根本没有人评论过性能。您能否将其放入答案中,也许可以扩展一点?
  • 标准库的关键点是priority_queue 是根据heap* 算法实现的,来自<algorithm>,应用于底层随机访问容器。
  • @KerrekSB 如果您将所有这些都放在答案中会很棒

标签: c++ algorithm sorting priority-queue


【解决方案1】:

优先级队列only 允许您按排序顺序访问 一个 元素——即,您可以获得最高优先级的项目,当您移除它时,您可以获得下一个最高优先级,依此类推。优先级队列还允许重复元素,因此它更像是一个多重集而不是一个集。 [编辑:正如@Tadeusz Kopec 指出的那样,构建一个堆也与堆中的项目数成线性关系,其中构建一个集合是 O(N log N) 除非它是从已经排序的序列构建的(在这种情况下它也是线性的)。]

集合允许您按排序顺序进行完全访问,例如,您可以在集合中间的某个位置找到两个元素,然后按顺序从一个元素遍历到另一个元素。

【讨论】:

  • 另一个区别是从给定的一组值构建优先级队列只有线性复杂度。
  • 性能方面,我发现在模拟我们拥有的用例的行为时,多重集的性能优于优先级队列。在我们的现实世界应用程序中,两者都可以正常运行,但一组更丰富的功能非常重要,因此总体而言使其成为赢家。 YMMV,但我怀疑在大多数情况下多集是更好的选择。
  • @TadeuszKopec 使用带有提示迭代器的emplace_hintinsert 也可以实现排序输入的线性复杂度。
【解决方案2】:

std::priority_queue 允许执行以下操作:

  1. 插入元素O(log n)
  2. 获取最小元素O(1)
  3. 擦除最小的元素O(log n)

std::set 有更多的可能性:

  1. 插入任意元素O(log n),常数大于std::priority_queue
  2. 找到任何元素O(log n)
  3. 找到一个元素,>= 比您要查找的元素 O(log n) (lower_bound)
  4. 擦除任何元素O(log n)
  5. iteratorO(1)擦除any元素
  6. 按排序顺序移动到上一个/下一个元素O(1)
  7. 获取最小元素O(1)
  8. 获取最大元素O(1)

【讨论】:

  • 或者可能在O(log n)中按排序顺序移动到上一个/下一个元素 - 不认识自己:(
  • 对于set,获取最小和最大的元素,应该是O(1)还是O(log n)。这个答案与 Andrew Tomazos 的答案相矛盾。哪个是正确的?
  • 对于集合,选择最小的元素实际上是*s.begin(),最大的元素是*s.rbegin(),所以由于这两个函数都有恒定的复杂性,我相信O(1)是正确的。 en.cppreference.com/w/cpp/container/set/begin
  • 如果 set 被构造为 BST,我们如何在 O(1) 时间内找到 begin()? rbegin() 也有类似的问题。除非我们有两个额外的 O(1) 空间来跟踪最大值和最小值(我不确定 STL 中是否是这种情况)。
  • @RobertWang This is the case in STL。所以O(1) 是正确答案。
【解决方案3】:

set/multiset 通常由二叉树支持。 http://en.wikipedia.org/wiki/Binary_tree

priority_queue 通常由堆支持。 http://en.wikipedia.org/wiki/Heap_(data_structure)

所以问题是什么时候应该使用二叉树而不是堆?

这两种结构都是一棵树,但是关于祖先之间关系的规则是不同的。

我们称位置为 P 为父,L 为左子,R 为右子。

在二叉树中 L

在堆中 P

因此,二叉树“横向”排序,而堆“向上”排序。

因此,如果我们将其视为一个三角形,那么在二叉树中 L、P、R 是完全排序的,而在堆中 L 和 R 之间的关系是未知的(只有它们与 P 的关系)。

这有以下效果:

  • 如果您有一个未排序的数组并且想要将其转换为二叉树,则需要 O(nlogn) 时间。如果你想把它变成一个堆只需要O(n)时间,(因为它只是比较找到极端元素)

  • 如果您只需要极端元素(某些比较函数的最低或最高),堆会更有效。堆只进行必要的比较(懒惰地)以确定极端元素。

  • 二叉树执行对整个集合排序所需的比较,并始终保持整个集合排序。

  • 堆具有最低元素的恒定时间查找 (peek),二叉树具有最低元素的对数时间查找。

【讨论】:

  • 这里没有详细说明。我要求的是不同的情况,在这些情况下你更愿意使用其中一种。
  • 在我看来,仅仅发布一个写了一半的答案来成为第一个回答问题的人并不是很好。
  • @penelope:我认为在此期间立即给出简短的回答比等待冗长的回答更有用。
  • 您能否详细说明在使用priority_queue 时将O(n) 中的未排序数组转换为堆。据此:cplusplus.com/reference/algorithm/push_heappush_heapO(log(n)),因此将元素一个接一个地推送到优先级队列似乎需要O(n log(n))。也许如果您一次构建堆,它会更快。是否可以在O(n) 时间从未排序的集合构造priority_queue
  • 对于多重集,查看最低元素是 O(1)(即 *begin() )
【解决方案4】:

由于std::priority_queuestd::set(和std::multiset)都是存储元素并允许您以有序方式访问它们的数据容器,并且具有相同的插入复杂性O(log n),使用一个比另一个(或者,什么样的情况需要一个或另一个?)?

尽管两个容器的 inserterase 操作具有相同的复杂度 O(log n),但 std::set 的这些操作是比std::priority_queue 慢。那是因为std::set 分配了很多内存。 std::set 的每个元素都存储在自己的分配中。 std::priority_queue(默认使用底层 std::vector 容器)使用单一分配来存储所有元素。另一方面,std::priority_queue 在其元素上使用了许多交换操作,而std::set 仅使用指针交换。因此,如果元素类型的交换操作非常缓慢,则使用std::set 可能更有效。此外,元素可能根本不可交换。

std::set 的内存开销要大得多,因为它必须在其节点之间存储许多指针。

【讨论】:

  • 非常感谢。你回答了我的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-28
  • 2021-10-20
  • 2014-03-04
  • 2013-01-18
  • 2015-07-21
  • 2012-10-27
相关资源
最近更新 更多