【问题标题】:std::priority_queue and make_heap API designstd::priority_queue 和 make_heap API 设计
【发布时间】:2016-10-15 14:25:02
【问题描述】:

我正在阅读有关 priority_queue(基本上是 make_heap 的包装器)的文档,发现您可以使用比较函数对其进行自定义。

来自文档(http://en.cppreference.com/w/cpp/container/priority_queue):

可以提供用户提供的比较来更改排序,例如 使用 std::greater 会导致最小元素显示为 顶部()。

在维基百科和其他 CS 文本中,堆是这样定义的:

在计算机科学中,堆是一种专门的基于树的数据结构 满足堆属性:如果 A 是 B 的父节点,则 节点 A 的键(值)相对于节点 B 的键进行排序 在整个堆中应用相同的顺序。一个堆可以 进一步分类为“最大堆”或“最小堆”。 最大 heap,父节点的key总是大于等于 那些孩子的,最高的关键在根节点。

但在 std::priority_queue 实现中,提供 std::greater 会导致创建 MinHeap(顶部的最小元素)。如果(父比较器子项)为真,我会期望一个最大堆,因为堆排序是定义的(在我读过的所有文献中)。

我发现这种 API 设计令人困惑。

有理由这样定义吗?

【问题讨论】:

标签: c++ algorithm c++11 priority-queue


【解决方案1】:

这是让你头疼的事情 - 提供 std::greater 实际上会产生最小堆。

原因如下:从堆的角度来看,比较器定义了堆上的小于关系,即使它实际上正在做其他事情。当std::priority_queue 需要插入元素时,它会调用比较器并给它两个元素。如果比较器返回true,它认为第一个元素小于第二个元素,并把第二个元素放在第一位(因为std::priority_queue 是一个最大堆实现)。结果,你最终得到了最小堆。

【讨论】:

  • 我了解它是如何实现的,但是为什么要这样定义呢?为什么不直接使用比较器来验证父/子关系,就像几乎在任何地方都定义的那样?
  • 为什么 max_heap 需要 std::less 而不是 std::greater?它只需要在 if 中切换 2 个元素的顺序,一切都会起作用 - 生成一个带有 std::greater 的 maxHeap。我的第一个猜测是它是某种优化,但我不确定这是它背后的实际原因。例如,std::sort API 以另一种(直观的)方式工作,如果比较器为真,则事情保持不变......
  • @AlexandruEne std::greater 是逆序少。我假设 C++ 默认实现了一个最大堆,因为它使堆排序稍微容易一些(将最大的元素移动到数组的末尾,也就是新打开的槽)。
  • @AlexandruEne:“我了解它是如何实现的”然而,正如 Sergey 指出的那样,您对行为的描述是错误的。
  • 在这个问题中,我根据文献描述了我的期望,而不是实现,我试图理解“为什么选择这个实现”,因为在我看来它会导致一个令人困惑的界面。背后的原因是什么?为什么比较器方法不定义父子关系?我可以看到代码在做什么,但我不明白为什么选择这种路线和设计。
【解决方案2】:

我也觉得它令人困惑,但从不同的角度来看。假设您要按升序对序列进行排序。有几种方法:

  1. 将您的数据放入std::vectorstd::deque 并使用std::less 运行std::sort()
  2. 将您的数据放入std::list 并使用std::less 运行std::list::sort()
  3. 将您的数据插入到配置有 std::lessstd::set 中,最后自动排序。
  4. 将您的数据放入std::vectorstd::deque 并运行std::make_heap(),然后使用std::less运行std::pop_heap()-s。
  5. 使用 std::greater (!!!) 通过std::priority_queue 推送您的数据。

正如我们所见,std::priority_queue 从这个角度来看是一个明显的异常值。

实际上,std::priority_queue 在这方面令人困惑的行为背后的原因隐藏在第 (4) 项中,因为这就是 std::priority_queue 在下面所做的。 (4) 也违背了我的直觉(尽管在较小程度上),因为在中间状态(虽然并非所有std::pop_heap 都已执行)序列的排序部分在其上限范围内,而不是在下限范围内。

但它也解释了为什么为标准库选择了最大堆 - std::pop_heap 将弹出的元素放在可以在恒定时间内从该位置移除的位置,而不管使用的容器类型如何。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-25
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 2013-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多