Data structures 在二进制堆中批量更新节点优先级?

Data structures 在二进制堆中批量更新节点优先级?,data-structures,binary-heap,Data Structures,Binary Heap,我发布了一个相当混乱的问题,所以我从头重写了它 这实际上是一个纯粹的理论问题 比如说,我们有二进制堆。让堆成为MaxHeap,这样根节点的值最大,每个节点的值都比它的子节点大。我们可以在此堆上执行一些常见的低级操作:“交换两个节点”、“比较两个节点” 使用这些低级操作,我们可以实现通常的高级递归操作:“向上筛选”、“向下筛选” 使用这些筛选和筛选,我们可以实现“插入”、“修复”和“更新”。我对“更新”功能感兴趣。假设我已经有了要更改的节点的位置。因此,更新功能非常简单: function upd

我发布了一个相当混乱的问题,所以我从头重写了它

这实际上是一个纯粹的理论问题

比如说,我们有二进制堆。让堆成为MaxHeap,这样根节点的值最大,每个节点的值都比它的子节点大。我们可以在此堆上执行一些常见的低级操作:“交换两个节点”、“比较两个节点”

使用这些低级操作,我们可以实现通常的高级递归操作:“向上筛选”、“向下筛选”

使用这些筛选和筛选,我们可以实现“插入”、“修复”和“更新”。我对“更新”功能感兴趣。假设我已经有了要更改的节点的位置。因此,更新功能非常简单:

function update (node_position, new_value){
    heap[node_position] = new_value;
    sift_up(node_position);
    sift_down(node_position);
}
我的问题是:是否(数学上)有可能实现更高级的“更新”功能,可以一次更新更多节点,在某种程度上,所有节点都将其值更改为新的_值,然后纠正其位置?大概是这样的:

function double_update (node1_pos, node2_pos, node1_newVal, node2_newVal){
    heap[node1_pos] = node1_newVal;
    heap[node2_pos] = node2_newVal;
    sift_up(node1_position);
    sift_down(node1_position);
    sift_up(node2_position);
    sift_down(node2_position);
}
我用这个“双_更新”做了一些测试,它成功了,尽管没有证明什么

那么“三重更新”呢,等等

我用“多次更新”做了一些其他的测试,我更改了所有节点的值,然后按随机顺序为每个节点调用{sift-up();sift-down();}。这不起作用,但结果离正确不远


我知道这听起来没什么用,但我对它背后的理论感兴趣。如果我让它工作,我确实有一个用途。

这是绝对可能的,但是如果你计划在二进制堆中更改大量的键,你可能想看看其他堆结构,比如斐波那契堆或配对堆,它们可以比二进制堆快得多。在具有n个节点的二进制堆中更改k个键需要O(k logn)时间,而在斐波那契堆中则需要O(k)时间。这是渐近最优的,因为在不做至少Ω(k)功的情况下,您甚至不能接触k个节点

另一个要考虑的是,如果你一次改变Ω(n/log n)键,你至少要做Ω(n)的工作。在这种情况下,通过使用标准的heapify算法在Θ(n)时间内从头开始重建堆,实现更新可能会更快


希望这有帮助

对于funky的一些定义,这里有一个技巧和可能的funky算法:

(为了表达想法,遗漏了很多东西):

模板类伪堆{
私人:
使用迭代器=类型名向量::迭代器;
迭代器max_节点;
向量堆;
bool-heapified;
void find_max(){
max_node=std::max_元素(heap.begin(),heap.end());
}
公众:
无效更新(迭代器节点,T new_val){
如果(节点==最大节点){
if(新值<*最大值节点){
heapified=false;
*最大节点=新值;
查找_max();
}否则{
*最大节点=新值;
}
}否则{
如果(新值>*最大值节点)最大值节点=新值;
*节点=新值;
heapified=false;
}
T&front(){return&*max_node;}
void pop_front(){
如果(!heapified){
std::iter_交换(vector.end()-1,max_节点);
std::make_heap(vector.begin(),vector.end()-1);
heapified=true;
}否则{
std::pop_堆(vector.begin(),vector.end());
}
}        
};
保持堆是昂贵的。如果在开始弹出堆之前进行
n
更新,则所做的工作与在需要对向量进行排序时对向量进行排序相同(
O(n log n)
)。如果始终知道最大值是什么是有用的,那么就有理由保留堆,但是如果最大值不比任何其他值更容易修改,那么您可以在摊余成本O(1)(即
1/n
乘以成本
O(n)的情况下随时保留最大值
剩下的时间是
O(1)
。这就是上面的代码所做的,但最好也懒得计算最大值,使
front()
摊销
O(1)
而不是常数
O(1)
。这取决于您的要求

另一种选择是,如果修改通常不会导致值移动太远,只需执行一个简单的“找到新的原点并旋转子向量”循环,虽然它是
O(n)
而不是
O(logn)
,但在短时间内移动时速度更快,因为常数更小


换句话说,除非经常需要查找前k个值,否则不要使用优先级堆。当读取之间有大量修改时,通常有更好的方法。

我正在使用heapify算法构建堆。大多数情况下,我一次只更新两个节点。可以通过调用update()来完成两次=O(2*logn)。但这是一个模拟脚本,在需要节点位置校正之前,节点的实际值将被随机更改很多次。我可以将位置校正与实际值更新分开,只要我总是一次只处理一个节点,情况就不是这样了。我实际上在寻找一种方法来分离po一次处理2个节点(通常)时,由于值的变化而产生的错误。顺便说一句,我知道这是可以做到的,我总是可以在节点值和堆之间添加分离层,制作“假节点”并仅在需要时更新它们,以消除无用的堆更新开销。但我更感兴趣的是数学技巧和时髦的算法:)也许我本不该说funky,但你们实际上给我带来了两个重要的理解:最好不要依赖学术结构,我也不应该花时间搜索可能不存在的完美算法。所以我完全摆脱了那个堆,当我看到它在实际情况中的表现时,我不会
template<typename T> class pseudoHeap {
  private:
    using iterator = typename vector<T>::iterator;
    iterator max_node;
    vector<T> heap;
    bool heapified;

    void find_max() {
      max_node = std::max_element(heap.begin(), heap.end());
    }

  public:
    void update(iterator node, T new_val) {
      if (node == max_node) {
          if (new_val < *max_node) {
               heapified = false;
               *max_node = new_val;
               find_max();
           } else {
               *max_node = new_val;
           }
      } else {
          if (new_val > *max_node) max_node = new_val;
          *node = new_val;
          heapified = false;
     }

    T& front() { return &*max_node; }

    void pop_front() {
        if (!heapified) {
            std::iter_swap(vector.end() - 1, max_node);
            std::make_heap(vector.begin(), vector.end() - 1);
            heapified = true;
        } else {
            std::pop_heap(vector.begin(), vector.end());
        }
    }        
};