使用 `std::greater` 通过 `priority_queue` 创建最小堆的原因

Posted

技术标签:

【中文标题】使用 `std::greater` 通过 `priority_queue` 创建最小堆的原因【英文标题】:The reason of using `std::greater` for creating min heap via `priority_queue` 【发布时间】:2015-12-21 06:32:40 【问题描述】:

我想知道为什么要使用priority_queue 创建最小堆,应该使用std::greater

std::priority_queue<T, std::vector<T>, std::greater<T> > min_heap;

对我来说,因为最小值总是位于堆的顶部,所以使用的类应该是std::less

更新: 另一方面,由于priority_queue(最大堆)的默认行为是在顶部保存最大值,所以在我看来std::greater应该用于创建最大堆而不是创建最小堆

【问题讨论】:

你在看哪里?我现在正在阅读 cppreference.com,他们将 std::less 指定为默认值,并说替换 std::greater 会导致最小元素显示为“顶部”而不是最大元素。似乎只是约定俗成的问题,不是吗? 我认为这是一个很好的问题。我觉得奇怪的是,很少有人质疑这个特定的设计决定。到目前为止,您是唯一一个像我一样认为比较器的这种“反向”使用非常违反直觉的人。我不会质疑这个决定背后的绩效原因,但这对我来说并不自然。 我自己在回答另一个问题时遇到了这个问题,编写自己的比较器时感觉特别不自然。 这有点奇怪。 heapify_down 是:如果更大,则将其向下移动。和 heapify_up:如果不是更大,则向上移动。 【参考方案1】:

C++ 堆函数make_heappush_heappop_heap 对max heap 进行操作,这意味着在使用默认比较器时,顶部元素是最大值。所以,要创建一个最小堆,你需要使用greater&lt;T&gt; 而不是less&lt;T&gt;

我怀疑使用最大堆而不是最小堆是因为使用less 操作更容易实现。在 C++ 中,less 具有作为所有 STL 算法的“默认”比较器的特殊特权;如果你只打算实现一个比较操作(== 除外),它应该是&lt;。这导致了一个不幸的怪癖,priority_queue&lt;T, C&lt;T&gt;, less&lt;T&gt;&gt; 表示最大队列,priority_queue&lt;T, C&lt;T&gt;, greater&lt;T&gt;&gt; 表示最小队列。

此外,nth_element 等某些算法需要最大堆。

【讨论】:

这并不能回答为什么使用less 会导致最大堆,而greater 会导致最小堆。 查看我的编辑,但 TL;DR 是最大堆在 C++ 的其他地方使用,因此它们是默认值。 所以,让我看看我是否理解正确。你的意思是因为less 是默认比较器,并且因为max_heap 更有用,所以我们最终得到了一个通过less 而不是greater 实现的max_heap 我不认为最大堆比使用更少的最小堆更容易实现。但是,从最大堆中实现std::sort_heap 肯定更容易(也更有效),假设您希望使用相同的比较运算符从std::sort 获得相同的排序。这一事实可能有助于推理。 @TemplateRex:因为当您从堆中弹出一个元素时,最后会留下一个空格,您可以在其中放置刚刚弹出的元素(这是最大的元素)。如果您从最小堆开始,为了获得正确的顺序,您必须在完成后反转范围。【参考方案2】:

见http://en.cppreference.com/w/cpp/container/priority_queue。 priority_queue 旨在将最大值放在顶部。如果您使用默认的std::less 比较器,就会发生这种情况。所以如果你想要反向行为,你需要使用反向比较器,std::greater

【讨论】:

但是为什么我应该使用less 而不是greater 将最大值放在顶部?【参考方案3】:

逻辑论证如下

    std::priority_queue 是容器适配器;基本的内存考虑使后面成为序列容器(例如std::vector)修改的首选位置(使用pop_back()push_back())。 priority_queue 原语基于 std::make_heap(构造函数)、std::pop_heap + container::pop_back (priority_queue::pop) 和 container::push_back + std::push_heap (priority_queue::push) pop_heap 将把底层存储的front,放在back,之后恢复堆不变。 push_heap 则相反。 在max_heap 上执行sort_heap(最初最大值位于前面)将repeatedly pop the front to the back 并根据less(这是默认比较运算符)对范围进行排序 因此,max_heap 的首选实现是让最大元素 w.r.t. less 在前面,通过priority_queue::top(底层container::front)访问。 人们仍然可以争论priority_queuestd::less 比较器是否代表max_heap 是否直观。它可以通过反转比较器的参数来定义为min_heap(但请参阅@T.C. 的评论,在对各种堆函数的调用中,使用 C++98 绑定器这是相当冗长的)。一个(对我而言)违反直觉的结果是 top() 不会给予具有 top 优先级的元素

【讨论】:

meow_heap 算法绝对是 C++98 中的。 @T.C.你是对的,更新了,只添加了is_heapis_heap_until 另外,你不能否定比较器;您需要一个交换参数顺序的包装器。考虑到最初设计时 C++ TMP 是多么原始(想想所有ptr_fun/bind1st/bind2nd 的乐趣),我并不感到惊讶他们没有这样做。 @T.C.是的,很容易忘记旧活页夹的痛苦,还必须提取first_argument_typesecond_argument_type 有人能再解释一下第 5 点吗?据我了解,从 max_heap 弹出元素是从后面完成的。因此,随着 less 按升序排列元素,获取顶部元素会从后面移除元素?我理解正确了吗?

以上是关于使用 `std::greater` 通过 `priority_queue` 创建最小堆的原因的主要内容,如果未能解决你的问题,请参考以下文章

错误:无法将“minHeap”从“std::priority_queue,std::greater >”转换为“std::priority_queue”

如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?

将 STL priority_queue 与 Greater_equal 比较器类一起使用

使用 Rails.cache 时 Rspec 测试失败,但如果我执行 binding.pry 则通过

215. 数组中的第K个最大元素

pro文件和pri文件