如何从 std::heap 弹出最小元素

How to pop min element from std::heap?

本文关键字:元素 heap std      更新时间:2023-10-16

我有一个std::vector,我在上面用std::heap

我想在 while 循环中,每次循环开始时弹出最小值。在循环的主体中,另一个元素将插入到堆中。循环在多次运行后停止。

我想到的是使用std::sort_heap然后擦除第一个元素。但是,这似乎不是一个好主意。你看,速度在我的代码的这一部分是真正重要的。

我认为这不是一个好主意,因为我必须在每次运行时调用std::sort_heap,因为将要插入一个元素,从而使std::vector再次堆,这意味着我必须再次对其进行排序,以便将最小元素放在前面。

此外,从头开始擦除vector.erase()比擦除最后一个元素慢,对吧?如果我删除第一个元素,那么所有其他元素都必须向左移动一个位置。如果我删除最后一个元素,我只需支付删除费用。

这让我想到了这个想法,按照我的方式对向量进行排序,以便最小元素将走到尽头,从而只支付最后一个元素的删除。但是,这仍然需要在循环的每次运行时进行排序,对吧?

解决这个问题的最佳方法是什么?

使用std::less排序的std::heap可以轻松提取最大的元素,而不是最小的元素。 但是,如果使用 std::greater ,则可以有效地提取 min 元素。

std::pop_heap ,使用 std::greater ,会将最小元素放在序列的后面,并将堆的大小减少 1。 然后,您可以读取back()并使用新元素写入back()

std::push_heap,使用 std::greater ,将元素插入序列的后面,将堆的大小增加 1。

在代码中:

#include <algorithm>
#include <vector>
#include <iostream>
int
main()
{
    std::vector<int> v{5, 4, 6, 9, 0, 1, 2, 3};
    std::make_heap(v.begin(), v.end(), std::greater<int>{});
    int i;
    while (std::cin >> i)
    {
        // pop minimum, and place at back()
        std::pop_heap(v.begin(), v.end(), std::greater<int>{});
        // write min out
        std::cout << "min = " << v.back() << 'n';
        // push a new element into the heap
        v.back() = i;
        std::push_heap(v.begin(), v.end(), std::greater<int>{});
    }
}

这一切都在使用vector非常有效,因为提取和插入实际上位于vector的后面。 而且因为我们使用 greater ,我们实际上每次都提取最小元素(这有点不直观(。

如果需要,上述逻辑可以封装在一个std::priority_queue<T, std::vector<T>, std::greater<T>>中,该简单地适应vector并公开pushpoptop成员函数,而不是vectorpush_backpop_backback。 这个适配器将简单地调用make_heappush_heap并在适当的位置pop_heap

更新

模板Rex正确地指出,在C++14中,greater<>可以用来代替greater<int>。 例如:

std::make_heap(v.begin(), v.end(), std::greater<>{});

这将创建一个异构函子,它可以接受任何两个参数,而不仅仅是int

template <>
struct greater<void>
{
    template <class T1, class T2>
    auto operator()(T1&& t, T2&& u) const
        { return std::forward<T1>(t) > std::forward<T2>(u); }
    typedef void is_transparent;
};

有了这个新功能,只需更改vector即可更改类型(以及i的类型(,示例的其余部分将自动适应类型的更改。

我认为你只是想要一个std::priority_queue<T>.它具有O(log n)推送和弹出的复杂性。

看看pop_heap和push_heap。正如 sharth 所建议的那样,您还可以使用 priority_queue 容器适配器,该适配器通过方便的优先级队列接口包装堆功能。

std::sort_heap不会创建堆 - 相反,它将堆作为其输入,并生成完全排序的元素作为其输出。

您可以按照@Sharth的建议使用priority_queue。如果您希望直接使用堆,则可以使用 push_heappop_heap 分别删除最小的项目并插入新项目。

请注意,与大多数算法不同,push_heappop_heap 只是重新排列范围内的项目。 即,pop_heap移动堆中最小(或最大,取决于您的比较函数(项目,并将其移动到范围的末尾,并将前面的元素重新排列到有效的堆中。

同样,push_heap 在内存的一个部分中获取一个堆,然后在内存中的下一个位置获取一个项目,并将该元素添加到堆中,因此整个元素再次形成一个有效的堆。