【问题标题】:How do I perform a deletion of the kth element on a min-max heap?如何删除最小最大堆上的第 k 个元素?
【发布时间】:2016-09-08 13:54:50
【问题描述】:

最小-最大堆对于实现双端优先级队列很有用,因为它的find-minfind-max 操作时间恒定。我们还可以在 O(log2 n) 时间内检索 min-max 堆中的最小和最大元素。但是,有时我们可能还想删除 min-max 堆中的任何节点,这可以在 O(log2 n) 中完成,根据 @ 987654321@:

...

该结构还可以泛化为支持常数时间的运算Find(k)(确定结构中的第k个最小值)和运算Delete(k)(删除结构中的第k个最小值)结构)在对数时间内,对于k 的任何固定值(或值集)。

...

究竟如何删除最小-最大堆上的第 k 个元素?

【问题讨论】:

    标签: algorithm data-structures asymptotic-complexity operation minmax-heap


    【解决方案1】:

    我不认为自己是算法和数据结构领域的“专家”,但我确实对二进制堆有详细的了解,包括最小-最大堆。例如,请参阅我关于二进制堆的博客系列,以 http://blog.mischel.com/2013/09/29/a-better-way-to-do-it-the-heap/ 开头。我有一个最小-最大实现,我会在某个时候开始写。

    Your solution to the problem 是正确的:当您删除任意节点时,您确实必须冒泡或筛选以重新调整堆。

    删除最小最大堆中的任意节点与最大堆或最小堆中的相同操作没有根本的不同。例如,考虑删除最小堆中的任意节点。从这个最小堆开始:

          0
      4       1
    5   6   2   3
    

    现在,如果您删除节点 5,您将拥有:

          0
      4       1
        6   2   3
    

    你把堆中的最后一个节点3,放到5所在的地方:

          0
      4       1
    3   6   2   
    

    在这种情况下,您不必筛选,因为它已经是一片叶子,但它不合适,因为它比它的父级小。您必须将其冒泡才能获得:

          0
      3       1
    4   6   2   
    

    相同的规则适用于最小-最大堆。您将要删除的元素替换为堆中的最后一项,并减少计数。然后,您必须检查是否需要将其冒泡或筛选。唯一棘手的部分是逻辑根据项目是处于最低级别还是最高级别而有所不同。

    在您的示例中,第一次操作产生的堆(将 55 替换为 31)是无效的,因为 31 小于 54。因此您必须将其向上冒泡。

    另一件事:删除任意节点确实是一个 log2(n) 操作。但是,查找要删除的节点是一个 O(n) 操作,除非您有一些其他数据结构来跟踪节点在堆中的位置。因此,一般来说,移除任意节点被认为是 O(n)。

    【讨论】:

    • 首先,感谢您的反馈。好的,但你说我们必须“冒泡”“冒泡”。这不是独家的,对吧?我唯一的疑问是这种“冒泡”和“冒泡”是否可以在最小-最大堆的一部分中独立于其他部分执行......
    • @Nuncameesquecideti:当您将最后一个项目移动到其新位置时,有三种可能性:1)它在正确的位置; 2)它比它的父母小,所以它必须向上移动; 3)它比它的孩子大,所以它必须向下移动。与二叉堆一样,这种移动可以独立完成,因为影响一个子树(一个节点及其所有祖先和从属节点)的操作不会影响其他子树。
    • 这意味着可以使用“删除”操作(或以类似方式)来实现“替换”操作,对吧?
    • 优先级队列中的一个常见操作是increase_key,您可以在其中更改项目的优先级。这将对应于您提到的 replace 方法。而且,是的,它涉及更改节点,然后根据需要将其向上或向下移动。我认为 deletereplace 的一个特例,在这里你将要删除的节点替换为堆中的最后一个节点,减少计数,然后进行调整就像您使用 replace 一样。
    【解决方案2】:

    促使我开发这个解决方案(我不是 100% 肯定是正确的)的原因是我实际上找到了一个解决方案来删除最小-最大堆中的任何节点,但这是错误的。

    可以在here(用C++实现)和here(用Python实现)找到错误的解决方案。我将介绍刚才提到的错误Python的解决方案,每个人都更容易理解:

    解决方法如下:

    def DeleteAt(self, position):
        """delete given position"""
        self.heap[position] = self.heap[-1]
        del(self.heap[-1])
        self.TrickleDown(position)
    

    现在,假设我们有以下最小-最大堆:

    level 0                            10                        
    
    level 1                92                       56           
    
    level 2         41          54          23          11     
    
    level 3      69    51    55    65    37    31
    

    据我检查,这是一个有效的最小-最大堆。现在,假设我们要删除元素 55,它在从 0 开始的数组中将在索引 9 处找到(如果我计算正确的话)。

    上面的解决方案只是简单地将最后一个元素放入数组中,在本例中为 31,并将其放在位置 9:

    level 0                            10                        
    
    level 1                92                       56           
    
    level 2         41          54          23          11     
    
    level 3      69    51    31    65    37    55
    

    它将删除数组的最后一个元素(现在是 55),生成的最小-最大堆如下所示:

    level 0                            10                        
    
    level 1                92                       56           
    
    level 2         41          54          23          11     
    
    level 3      69    51    31    65    37 
    

    最后它会从position“涓涓细流”(即现在我们有数字 31)。

    "tricle-down" 将检查我们是否处于 even(或 min)或 odd(或 max)级别:我们处于 odd level (3),所以“trickle-down”会从 31 开始调用“trickle-down-max”,但由于 31 没有子级,它会停止(如果没有,请查看上面的原始论文知道我在说什么)。

    但是,如果您观察到使数据结构处于更多的最小-最大堆的状态,因为 54,它处于偶数级别,因此应该小于其后代,是大于 31,它的后代之一。


    这让我觉得我们不能只看position 的节点的子节点,但我们还需要从position 向上检查,也许我们需要使用“涓流”也是。

    在下面的推理中,让x 成为position 处的元素,在我们删除了我们想要删除的元素之后并且在任何修复操作运行之前。让p 成为它的父级(如果有的话)。

    我的算法的想法确实是这样的,更具体地说,是基于以下事实:

    1. 如果x 处于奇数级别(如上面的示例中),我们将其与其父级p 交换,后者处于偶数级别,这不会破坏 min- 的任何规则/不变量从新x的位置向下的最大堆。

      • 如果情况颠倒,也可以进行相同的推理(我认为),即 x 最初处于偶数位置,并且会大于其父级。

      • 现在,如果您注意到,唯一需要修复的是,如果 x 与其父级交换并且它现在处于偶数(分别为奇数)位置,我们可能需要检查是否它比前一个偶数(和奇数)级别的节点更小(并且分别更大)。

    这当然不是我的全部解决方案,当然我还想检查x 的前一个父级,即p,是否处于正确的位置。

    • 如果p,在与x 交换后,处于奇数(和分别为偶数)级别,这意味着它可能小于(或分别大于)它的任何后代,因为它是以前处于偶数(和分别为奇数)级别。所以,我认为我们需要在这里“涓滴”。

    • 关于 p 相对于其祖先是否处于正确位置这一事实,我认为推理类似于上述推理(但我不是 100% 确定)。

    综合起来,我想出了解决方案:

    function DELETE(H, i):
    
        // H is the min-max heap array
        // i is the index of the node we want to delete
        // I assume, for simplicity, 
        // it's not out of the bounds of the array
    
        if i is the last index of H:
            remove and return H[i]
        else:
            l = get_last_index_of(H)
    
            swap(H, i, l)  
    
            d = delete(H, l)
    
            // d is the element we wanted to remove initially
            // and was initially at position i
            // So, at index i we now have what was the last element of H
    
            push_up(H, i)
    
            push_down(H, i)
    
            return d
    

    这似乎根据我制作的最小-最大堆的实现工作,您可以找到here

    还要注意,解决方案在 O(log2 n) 时间内运行,因为我们只是调用“push-up”和“push-down”按此顺序运行。

    【讨论】:

      猜你喜欢
      • 2019-04-11
      • 2015-10-06
      • 2019-08-13
      • 1970-01-01
      • 2015-01-15
      • 2019-12-04
      • 2020-12-28
      • 2014-07-21
      • 2012-05-18
      相关资源
      最近更新 更多