使用C ++标准库以对数时间进行Heapify

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用C ++标准库以对数时间进行Heapify相关的知识,希望对你有一定的参考价值。

我有一堆使用std::make_heap

std::vector<int> v{1,2,3,5,9,20,3};
std::make_heap(v.begin(), v.end());

现在我通过更改一个随机元素来更新堆:

v[3] = 35;

有没有办法在标准库中再次在O(log n)时间调整堆,其中n是容器的大小。基本上我在寻找heapify函数。我知道哪个元素已被更改。

据我所知,std::make_heapO(n log n)的时间。我也经历了重复的问题但是在改变最大元素的意义上是不同的。因为在那个问题上已经给出了O(log n)复杂性的解决方案。

我试图改变堆内的任何随机元素。

答案

你可以自己做:

void modify_heap_element(std::vector<int> &heap, size_t index, int value)
{
    //while value is too large for its position, bubble up
    while(index > 0 && heap[(index-1)>>1] < value)
    {
        size_t parent = (index-1)>>1;
        heap[index]=heap[parent];
        index = parent;
    }
    //while value is too large for its position sift down
    for (;;)
    {
        size_t left=index*2+1;
        size_t right=left+1;
        if (left >= heap.size())
            break;
        size_t bigchild = (right >= heap.size() || heap[right] < heap[left] ?
                           left : right );
        if (!(value < heap[bigchild]))
           break;
        heap[index]=heap[bigchild];
        index = bigchild;
    }
    heap[index] = value;
}
另一答案

如果我们仔细观察你的陈述:

现在我通过改变堆的一个随机元素来扰乱堆。

对于O(log n)中的堆积,你只能直接“干扰”向量的后面或前面(它以某种方式对应于插入或删除元素)。在这些情况下,可以通过std::push_heapstd::pop_heap算法实现(重新)堆积,这些算法采用对数运行时间。

那就是后面:

v.back() = 35;
std::push_heap(v.begin(), v.end()); // heapify in O(log n)

或前面:

v.front() = 35;

// places the front at the back
std::pop_heap(v.begin(), v.end()); // O(log n)
// v.back() is now 35, but it does not belong to the heap anymore

// make the back belong to the heap again
std::push_heap(v.begin(), v.end()); // O(log n)

否则,您需要使用std::make_heap重新整理整个矢量,这需要线性运行时间。


Summary

无法修改堆的任意元素并使用标准库(即函数模板std::push_heapstd::pop_heap)在对数运行时实现堆积。但是,您始终可以自己实现堆的swim和sink操作,以便在对数运行时进行堆积。

另一答案

我一直面临着想要“可更新堆”的问题。但是,最后,我解决了一些不同的问题,而不是编写自定义可更新堆或类似的东西。

要保持对最佳元素的访问而不需要显式遍历堆,可以使用要排序的元素的版本化包装。每个唯一的true元素都有一个版本计数器,每次元素更改时都会增加。然后堆中的每个包装器都带有一个元素版本,即创建包装器时的版本:

struct HeapElemWrapper
{
    HeapElem * e;

    size_t version;        
    double priority;

    HeapElemWrapper(HeapElem * elem)
     : e(elem), version(elem->currentVersion), priority(0.0)
    {}

    bool upToDate() const
    {
        return version == e->currentVersion;
    }

    // operator for ordering with heap / priority queue:
    // smaller error -> higher priority
    bool operator<(const HeapElemWrapper & other) const
    {
        return this->priority> other.priority;
    }
};

从堆中弹出最顶层的元素时,您可以简单地检查此包装元素以查看它是否与原始元素保持同步。如果没有,只需将其丢弃并弹出下一个。这种方法非常有效,我也可以在其他应用程序中看到它。你唯一需要注意的是你不时地通过堆来从过时的元素中清除它(比如每1000次插入一次)。

另一答案

It's not possible只需使用标准库提供的函数模板std::pop_heap()std::push_heap(),就可以在对数运行时修改堆的任意元素,而不会违反堆属性。

但是,您可以为此目的定义自己的类似STL的函数模板set_heap_element()

template<typename RandomIt, typename T, typename Cmp>
void set_heap_element(RandomIt first, RandomIt last, RandomIt pos, T value, Cmp cmp)
{
    const auto n = last - first;
    *pos = std::move(value); // replace previous value

    auto i = pos - first;
    using std::swap;

    // percolate up
    while (i > 0) { // non-root node
        auto parent_it = first + (i-1)/2;

        if (cmp(*pos, *parent_it))
            break; // parent node satisfies the heap-property 

        swap(*pos, *parent_it); // swap with parent
        pos = parent_it;
        i = pos - first;
    }

    // percolate down
    while (2*i + 1 < n) { // non-leaf node, since it has a left child
        const auto lidx = 2*i + 1, ridx = 2*i + 2;

        auto lchild_it = first + lidx; 
        auto rchild_it = ridx < n? first + ridx: last;

        auto it = pos;
        if (cmp(*it, *lchild_it))
            it = lchild_it;
        if (rchild_it != last && cmp(*it, *rchild_it))
            it = rchild_it;

        if (pos == it)
            break; // node satisfies the heap-property

        swap(*pos, *it); // swap with child
        pos = it;
        i = pos - first;
    }
}

然后,您可以为最大堆提供以下简化的set_heap_element()重载:

#include <functional> // std::less

template<typename RandomIt, typename T>
void set_heap_element(RandomIt first, RandomIt last, RandomIt pos, T value) {
    return set_heap_element(first, last, pos, value, std::less<T>{});
}

此重载使用std::less<T>对象作为原始函数模板的比较函数对象。


Example

在你的max-heap示例中,set_heap_element()可以使用如下:

std::vector<int> v{1,2,3,5,9,20,3};
std::make_heap(v.begin(), v.end());

// set 4th element to 35 in O(log n)
set_heap_element(v.begin(), v.end(), v.begin() + 3, 35);

你可以使用std::is_heap(),它需要线性时间,只要你想在上面用v函数模板设置元素后检查set_heap_element()是否仍然满足max-heap属性:

assert(std::is_heap(v.begin(), v.end())); 

What about min heaps?

通过将std::greater<int>对象作为函数调用的最后一个参数传递给std::make_heap()set_heap_element()std::is_heap(),可以实现最小堆的相同:

std::vector<int> v{1,2,3,5,9,20,3};
// create a min heap
std::make_heap(v.begin(), v.end(), std::greater<int>{});

// set 4th element to 35 in O(log n)
set_heap_element(v.begin(), v.end(), v.begin() + 3, 35, std::greater<int>{});

// is the min-heap property satisfied?
assert(std::is_heap(v.begin(), v.end(), std::greater<int>{}));

以上是关于使用C ++标准库以对数时间进行Heapify的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用非标准 C(gcc 特定功能)对 linux 内核进行编码? [关闭]

实例 :教你使用简单神经网络和LSTM进行时间序列预测(附代码)

使用 getopt() 进行 C 编程:给出命令行标志标准

在 C 中使用字符串进行二进制搜索

对同一文件的不同部分使用不同的 C++ 标准

C 编程:如何为 Unicode 编程?