Python有heapq
模块,实现了堆数据结构,并支持一些基本操作(push、pop)。
如何在O(log n)时间内从堆中删除第i个元素?使用heapq
是否可能实现,还是我需要使用另一个模块?
请注意,文档底部有一个示例:http://docs.python.org/library/heapq.html,其中提供了一种可能的方法-但这不是我想要的。我想要删除元素,而不仅仅是标记为已删除。
Python有heapq
模块,实现了堆数据结构,并支持一些基本操作(push、pop)。
如何在O(log n)时间内从堆中删除第i个元素?使用heapq
是否可能实现,还是我需要使用另一个模块?
请注意,文档底部有一个示例:http://docs.python.org/library/heapq.html,其中提供了一种可能的方法-但这不是我想要的。我想要删除元素,而不仅仅是标记为已删除。
你可以很容易地从堆中删除第i个元素:
h[i] = h[-1]
h.pop()
heapq.heapify(h)
只需将要删除的元素替换为最后一个元素,然后删除最后一个元素并重新对堆进行堆化。这个过程的时间复杂度为O(n),如果你想使用O(log(n))的时间复杂度,需要调用一些内部的堆化函数,或者更好的做法是像larsmans指出的那样,将_heapq.py中的_siftup / _siftdown源代码复制到你自己的代码中。
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
更快:稍微思考一下就会发现,heapify
可能是以 C 实现的,但内部函数的 C 实现没有暴露出来。如果性能很重要,则应考虑在典型数据上进行一些定时测试,以查看哪种方法最佳。除非您拥有真正巨大的堆,否则大 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 足够快是很有可能的,但对于大的堆来说,这是一种不必要的开销。i
引用最后一个元素时,_siftup()
调用将失败,但在这种情况下,从堆末尾弹出一个元素不会破坏堆的不变性。(a) 考虑为什么不想要懒惰删除。在很多情况下,这是正确的解决方案。
(b) 堆是一个列表。您可以像任何其他列表一样按索引删除元素,但然后需要重新堆化它,因为它不再满足堆不变量。
class RemoveByIndexHeap:
def __init__(self):
self.heap = []
def _swap(self, i: int, j: int):
self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
def _sink(self, i: int):
while i < self.size():
swap_with = i
if i * 2 + 1 < self.size() and self.heap[swap_with] > self.heap[i * 2 + 1]:
swap_with = i * 2 + 1
if i * 2 + 2 < self.size() and self.heap[swap_with] > self.heap[i * 2 + 2]:
swap_with = i * 2 + 2
if swap_with == i:
break
else:
self._swap(i, swap_with)
i = swap_with
def _swim(self, i: int):
while i > 0:
swap_with = i
if (i - 1) // 2 >= 0 and self.heap[swap_with] < self.heap[(i - 1) // 2]:
swap_with = (i - 1) // 2
if swap_with == i:
break
else:
self._swap(i, swap_with)
i = swap_with
def add(self, obj):
self.heap.append(obj)
self._swim(self.size() - 1)
def remove(self, index: int):
self._swap(index, self.size() - 1)
self.heap.pop()
if index != self.size():
self._sink(index)
self._swim(index)
def get_top(self):
if not self.heap:
return None
return self.heap[0]
def size(self):
return len(self.heap)
_siftup
函数的定义复制到程序中会更加清晰。链接 - Fred Foo_siftup
确实会抛出异常,但如果您删除最后一个元素,则不需要执行_siftup
或_siftdown
。更新了答案。 - Duncan