使用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_heap
是O(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_heap
和std::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_heap
和std::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 内核进行编码? [关闭]