促使我开发这个解决方案(我不是 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 成为它的父级(如果有的话)。
我的算法的想法确实是这样的,更具体地说,是基于以下事实:
-
如果x 处于奇数级别(如上面的示例中),我们将其与其父级p 交换,后者处于偶数级别,这不会破坏 min- 的任何规则/不变量从新x的位置向下的最大堆。
这当然不是我的全部解决方案,当然我还想检查x 的前一个父级,即p,是否处于正确的位置。
综合起来,我想出了解决方案:
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”按此顺序运行。