【问题标题】:Python: delete element from heapPython:从堆中删除元素
【发布时间】:2012-04-27 02:34:31
【问题描述】:

Python 有heapq 模块,它实现了堆数据结构,它支持一些基本操作(push、pop)。

如何在 O(log n) 中从堆中删除第 i 个元素?甚至可以使用heapq 还是我必须使用另一个模块?

注意,文档底部有一个示例: http://docs.python.org/library/heapq.html 这提出了一种可能的方法——这不是我想要的。我希望删除元素,而不仅仅是标记为已删除。

【问题讨论】:

    标签: python heap


    【解决方案1】:

    (a) 考虑一下为什么不想延迟删除。在很多情况下,这是正确的解决方案。

    (b) 堆是一个列表。您可以按索引删除元素,就像任何其他列表一样,但是您需要重新堆化它,因为它不再满足堆不变量。

    【讨论】:

    • 你能为 (b) 添加一些参考吗?
    • @Zenon b 的哪一部分?您可以在解释器中查看对象的类型,或阅读 OP 链接到的文档;至于需要重新堆,这是因为这样的操作会导致列表违反堆不变量(也在该文档中给出)。
    • (a) - 延迟删除是完全有效的,我只是想更好地理解这些堆。 (b) 我对至少 O(log n) 感兴趣,heapify 是 O(n)
    • 延迟删除是一种解决堆的 O(N) 删除成本的天才方法。
    • 对于任何想知道什么是“延迟删除”的人,您可以找到下面的文章,但基本上在这种情况下,您在键值存储中将元素标记为“已删除”,但实际上并未将其从堆,因为那将需要 O(n) 时间。然后,当您使用堆时,如果您正在查看的节点被标记为已删除,则可以检查该键值存储。它用于哈希表,但也可以在这里使用en.wikipedia.org/wiki/Lazy_deletion
    【解决方案2】:

    您可以很容易地从堆中删除第 i 个元素:

    h[i] = h[-1]
    h.pop()
    heapq.heapify(h)
    

    只需将要删除的元素替换为最后一个元素,然后删除最后一个元素,然后重新堆化堆。这是 O(n),如果你愿意,你可以在 O(log(n)) 中做同样的事情,但你需要调用几个内部 heapify 函数,或者更好,因为 larsmans 指出只需复制源_siftup/_siftdown 出 heapq.py 到你自己的代码中:

    h[i] = h[-1]
    h.pop()
    if i < len(h):
        heapq._siftup(h, i)
        heapq._siftdown(h, 0, i)
    

    请注意,在每种情况下,您都不能只执行h[i] = h.pop(),因为如果i 引用最后一个元素,那将失败。如果您在特殊情况下删除最后一个元素,那么您可以结合覆盖和弹出。

    请注意,根据堆的典型大小,您可能会发现仅调用 heapify 虽然理论上效率较低,但可能比重复使用 _siftup/_siftdown 更快:稍加反思就会发现 @ 987654329@ 可能在 C 中实现,但内部函数的 C 实现没有公开。如果性能对您很重要,那么考虑对典型数据进行一些时序测试,看看哪个是最好的。除非你有非常大的堆,否则 big-O 可能不是最重要的因素。

    编辑:有人试图编辑此答案以删除对_siftdown 的调用,并带有以下评论:

    _siftdown 不是必需的。新的 h[i] 保证是老 h[i] 的孩子中最小的,它仍然大于老 h[i] 的父母 (新 h[i] 的父级)。 _siftdown 将是无操作的。我必须编辑,因为我 还没有足够的代表来添加评论。

    他们在此评论中遗漏了h[-1] 可能根本不是h[i] 的孩子。在h[i] 插入的新值可能来自堆的完全不同的分支,因此可能需要在任一方向进行筛选。

    还有评论询问为什么不直接使用sort() 来恢复堆:调用_siftup_siftdown 都是O(log n) 操作,调用heapify 是O(n)。调用 sort() 是一个 O(n log n) 操作。调用 sort 很可能会足够快,但对于大堆来说这是不必要的开销。

    已编辑以避免@Seth Bruder 指出的问题。当i 引用结束元素时,_siftup() 调用将失败,但在这种情况下,从堆末尾弹出一个元素不会破坏堆不变量。

    【讨论】:

    • +1,附带说明将_siftup 的定义复制到@AlexMartelli here 推荐的程序中会更简洁。
    • @Duncan 我在这里有一个疑问,我正在尝试在优先级队列上实现 reduceKey 操作。在您的方法中,您假设 reduction 具有要删除的项目的索引(i)。如果我只有元素而不是索引,那怎么办?
    • 由于你不知道新的 h[i] 会大于还是小于它的父母或孩子,你还需要在之前或之后调用 heapq._siftdown(h, 0, i)调用_siftup
    • @Duncan 我认为@seaotternerd 的观点仍然成立:就像现在一样,_siftup() 的索引参数可能会索引刚刚被pop() 删除的元素,导致_siftup()扔。
    • @SethBruder,很好。是的,_siftup 确实会抛出,但如果您删除最后一个元素,则无需执行 _siftup_siftdown。相应地更新了答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 2021-05-27
    相关资源
    最近更新 更多