我在 Python 中使用啥来实现最大堆?
Posted
技术标签:
【中文标题】我在 Python 中使用啥来实现最大堆?【英文标题】:What do I use for a max-heap implementation in Python?我在 Python 中使用什么来实现最大堆? 【发布时间】:2011-01-30 21:10:39 【问题描述】:Python 包含用于最小堆的 heapq 模块,但我需要一个最大堆。我应该在 Python 中使用什么来实现最大堆?
【问题讨论】:
【参考方案1】:最好的方法:
Input:
from heapq import *
h = [5, 7, 9, 1, 3]
h_neg = [-i for i in h]
heapify(h_neg) # heapify
heappush(h_neg, -2) # push
print(-heappop(h_neg)) # pop
Output: 9
【讨论】:
【参考方案2】:heapq 模块 拥有实现 maxheap 所需的一切。 它只执行 max-heap 的 heappush 功能。 我在下面演示了如何克服下面的问题⬇
在 heapq 模块中添加这个函数:
def _heappush_max(heap, item):
"""Push item onto heap, maintaining the heap invariant."""
heap.append(item)
_siftdown_max(heap, 0, len(heap)-1)
最后加上这个:
try:
from _heapq import _heappush_max
except ImportError:
pass
瞧!完成了。
PS - 转到 heapq 函数。首先在编辑器中写入“import heapq”,然后右键单击“heapq”并选择 go to defintion。
【讨论】:
【参考方案3】:最简单的方法 是将每个元素都转换为负数,它会解决你的问题。
import heapq
heap = []
heapq.heappush(heap, 1*(-1))
heapq.heappush(heap, 10*(-1))
heapq.heappush(heap, 20*(-1))
print(heap)
输出将如下所示:
[-20, -1, -10]
【讨论】:
【参考方案4】:我还需要使用最大堆,并且我正在处理整数,所以我只是将 heap
中我需要的两个方法包装如下:
import heapq
def heappush(heap, item):
return heapq.heappush(heap, -item)
def heappop(heap):
return -heapq.heappop(heap)
然后我将我的heapq.heappush()
和heapq.heappop()
电话分别替换为heappush()
和heappop()
。
【讨论】:
【参考方案5】:解决方案是在将值存储在堆中时取反,或者像这样反转对象比较:
import heapq
class MaxHeapObj(object):
def __init__(self, val): self.val = val
def __lt__(self, other): return self.val > other.val
def __eq__(self, other): return self.val == other.val
def __str__(self): return str(self.val)
最大堆示例:
maxh = []
heapq.heappush(maxh, MaxHeapObj(x))
x = maxh[0].val # fetch max value
x = heapq.heappop(maxh).val # pop max value
但是你必须记住包装和解包你的值,这需要知道你是在处理最小堆还是最大堆。
MinHeap、MaxHeap 类
为MinHeap
和MaxHeap
对象添加类可以简化您的代码:
class MinHeap(object):
def __init__(self): self.h = []
def heappush(self, x): heapq.heappush(self.h, x)
def heappop(self): return heapq.heappop(self.h)
def __getitem__(self, i): return self.h[i]
def __len__(self): return len(self.h)
class MaxHeap(MinHeap):
def heappush(self, x): heapq.heappush(self.h, MaxHeapObj(x))
def heappop(self): return heapq.heappop(self.h).val
def __getitem__(self, i): return self.h[i].val
示例用法:
minh = MinHeap()
maxh = MaxHeap()
# add some values
minh.heappush(12)
maxh.heappush(12)
minh.heappush(4)
maxh.heappush(4)
# fetch "top" values
print(minh[0], maxh[0]) # "4 12"
# fetch and remove "top" values
print(minh.heappop(), maxh.heappop()) # "4 12"
【讨论】:
不错。我接受了这个并向 __init__ 添加了一个可选的list
参数,在这种情况下我调用heapq.heapify
并添加了一个heapreplace
方法。
很惊讶没有人发现这个错字:MaxHeapInt --> MaxHeapObj。否则,确实是一个非常干净的解决方案。
有趣的是包凡晨对这个问题的回答非常相似:***.com/questions/8875706/…
需要这条线吗? def __eq__(self, other): 返回 self.val == other.val。我认为没有它也可以工作。
@apadana 是的,拥有它很好 - 是否需要它取决于 heapify
实现以及您想对堆做什么。我们只需要定义__lt__
和__eq__
以方便MaxHeapObj
对象(、>=)之间的所有比较,例如搜索你的堆。【参考方案6】:
这是一个基于heapq
的简单MaxHeap
实现。虽然它只适用于数值。
import heapq
from typing import List
class MaxHeap:
def __init__(self):
self.data = []
def top(self):
return -self.data[0]
def push(self, val):
heapq.heappush(self.data, -val)
def pop(self):
return -heapq.heappop(self.data)
用法:
max_heap = MaxHeap()
max_heap.push(3)
max_heap.push(5)
max_heap.push(1)
print(max_heap.top()) # 5
【讨论】:
又好又简单! 最容易理解的代码,无需解释。【参考方案7】:为了详细说明 https://***.com/a/59311063/1328979,这里有一个完整记录、注释和测试的 Python 3 实现,用于一般情况。
from __future__ import annotations # To allow "MinHeap.push -> MinHeap:"
from typing import Generic, List, Optional, TypeVar
from heapq import heapify, heappop, heappush, heapreplace
T = TypeVar('T')
class MinHeap(Generic[T]):
'''
MinHeap provides a nicer API around heapq's functionality.
As it is a minimum heap, the first element of the heap is always the
smallest.
>>> h = MinHeap([3, 1, 4, 2])
>>> h[0]
1
>>> h.peek()
1
>>> h.push(5) # N.B.: the array isn't always fully sorted.
[1, 2, 4, 3, 5]
>>> h.pop()
1
>>> h.pop()
2
>>> h.pop()
3
>>> h.push(3).push(2)
[2, 3, 4, 5]
>>> h.replace(1)
2
>>> h
[1, 3, 4, 5]
'''
def __init__(self, array: Optional[List[T]] = None):
if array is None:
array = []
heapify(array)
self.h = array
def push(self, x: T) -> MinHeap:
heappush(self.h, x)
return self # To allow chaining operations.
def peek(self) -> T:
return self.h[0]
def pop(self) -> T:
return heappop(self.h)
def replace(self, x: T) -> T:
return heapreplace(self.h, x)
def __getitem__(self, i) -> T:
return self.h[i]
def __len__(self) -> int:
return len(self.h)
def __str__(self) -> str:
return str(self.h)
def __repr__(self) -> str:
return str(self.h)
class Reverse(Generic[T]):
'''
Wrap around the provided object, reversing the comparison operators.
>>> 1 < 2
True
>>> Reverse(1) < Reverse(2)
False
>>> Reverse(2) < Reverse(1)
True
>>> Reverse(1) <= Reverse(2)
False
>>> Reverse(2) <= Reverse(1)
True
>>> Reverse(2) <= Reverse(2)
True
>>> Reverse(1) == Reverse(1)
True
>>> Reverse(2) > Reverse(1)
False
>>> Reverse(1) > Reverse(2)
True
>>> Reverse(2) >= Reverse(1)
False
>>> Reverse(1) >= Reverse(2)
True
>>> Reverse(1)
1
'''
def __init__(self, x: T) -> None:
self.x = x
def __lt__(self, other: Reverse) -> bool:
return other.x.__lt__(self.x)
def __le__(self, other: Reverse) -> bool:
return other.x.__le__(self.x)
def __eq__(self, other) -> bool:
return self.x == other.x
def __ne__(self, other: Reverse) -> bool:
return other.x.__ne__(self.x)
def __ge__(self, other: Reverse) -> bool:
return other.x.__ge__(self.x)
def __gt__(self, other: Reverse) -> bool:
return other.x.__gt__(self.x)
def __str__(self):
return str(self.x)
def __repr__(self):
return str(self.x)
class MaxHeap(MinHeap):
'''
MaxHeap provides an implement of a maximum-heap, as heapq does not provide
it. As it is a maximum heap, the first element of the heap is always the
largest. It achieves this by wrapping around elements with Reverse,
which reverses the comparison operations used by heapq.
>>> h = MaxHeap([3, 1, 4, 2])
>>> h[0]
4
>>> h.peek()
4
>>> h.push(5) # N.B.: the array isn't always fully sorted.
[5, 4, 3, 1, 2]
>>> h.pop()
5
>>> h.pop()
4
>>> h.pop()
3
>>> h.pop()
2
>>> h.push(3).push(2).push(4)
[4, 3, 2, 1]
>>> h.replace(1)
4
>>> h
[3, 1, 2, 1]
'''
def __init__(self, array: Optional[List[T]] = None):
if array is not None:
array = [Reverse(x) for x in array] # Wrap with Reverse.
super().__init__(array)
def push(self, x: T) -> MaxHeap:
super().push(Reverse(x))
return self
def peek(self) -> T:
return super().peek().x
def pop(self) -> T:
return super().pop().x
def replace(self, x: T) -> T:
return super().replace(Reverse(x)).x
if __name__ == '__main__':
import doctest
doctest.testmod()
https://gist.github.com/marccarre/577a55850998da02af3d4b7b98152cf4
【讨论】:
【参考方案8】:继 Isaac Turner 的出色 answer 之后,我想举一个基于 K Closest Points to the Origin 使用最大堆的示例。
from math import sqrt
import heapq
class MaxHeapObj(object):
def __init__(self, val):
self.val = val.distance
self.coordinates = val.coordinates
def __lt__(self, other):
return self.val > other.val
def __eq__(self, other):
return self.val == other.val
def __str__(self):
return str(self.val)
class MinHeap(object):
def __init__(self):
self.h = []
def heappush(self, x):
heapq.heappush(self.h, x)
def heappop(self):
return heapq.heappop(self.h)
def __getitem__(self, i):
return self.h[i]
def __len__(self):
return len(self.h)
class MaxHeap(MinHeap):
def heappush(self, x):
heapq.heappush(self.h, MaxHeapObj(x))
def heappop(self):
return heapq.heappop(self.h).val
def peek(self):
return heapq.nsmallest(1, self.h)[0].val
def __getitem__(self, i):
return self.h[i].val
class Point():
def __init__(self, x, y):
self.distance = round(sqrt(x**2 + y**2), 3)
self.coordinates = (x, y)
def find_k_closest(points, k):
res = [Point(x, y) for (x, y) in points]
maxh = MaxHeap()
for i in range(k):
maxh.heappush(res[i])
for p in res[k:]:
if p.distance < maxh.peek():
maxh.heappop()
maxh.heappush(p)
res = [str(x.coordinates) for x in maxh.h]
print(f"k closest points from origin : ', '.join(res)")
points = [(10, 8), (-2, 4), (0, -2), (-1, 0), (3, 5), (-2, 3), (3, 2), (0, 1)]
find_k_closest(points, 3)
【讨论】:
【参考方案9】:如果您想使用最大堆获取最大的 K 元素,可以执行以下技巧:
nums= [3,2,1,5,6,4]
k = 2 #k being the kth largest element you want to get
heapq.heapify(nums)
temp = heapq.nlargest(k, nums)
return temp[-1]
【讨论】:
不幸的是,时间复杂度为 O(MlogM),其中 M = len(nums),这违背了 heapq 的目的。在此处查看nlargest
的实现和 cmets -> github.com/python/cpython/blob/…
感谢您提供信息丰富的评论,将确保检查所附链接。【参考方案10】:
最简单理想的解决方案
将值乘以 -1
给你。现在所有最高的数字都是最低的,反之亦然。
请记住,当您弹出一个元素以将其与 -1 相乘以再次获得原始值时。
【讨论】:
很好,但大多数解决方案都支持类/其他类型,并且不会更改实际数据。悬而未决的问题是,将值乘以 -1 是否不会改变它们(非常精确的浮点数)。 @AlexBaranowski。没错,但这是维护者的回应:bugs.python.org/issue27295 维护者有权不实现某些功能,但这个 IMO 实际上很有用。 这对于一些编码回合来说可能是一个很好的解决方案。否则在应用程序中更改数据听起来并不那么好。【参考方案11】:扩展 int 类并覆盖 __lt__ 是其中一种方法。
import queue
class MyInt(int):
def __lt__(self, other):
return self > other
def main():
q = queue.PriorityQueue()
q.put(MyInt(10))
q.put(MyInt(5))
q.put(MyInt(1))
while not q.empty():
print (q.get())
if __name__ == "__main__":
main()
【讨论】:
这是可能的,但我觉得它会减慢速度并使用大量额外的内存。 MyInt 也不能真正在堆结构之外使用。但是感谢您输入示例,这很有趣。 哈!在我发表评论的一天后,我遇到了需要将自定义对象放入堆中并需要最大堆的情况。实际上,我重新搜索了这篇文章并找到了您的答案,并以此为基础解决了我的问题。 (自定义对象是一个点,具有 x,y 坐标和 lt 覆盖比较距中心的距离)。感谢您发布此内容,我投了赞成票!【参考方案12】:你可以使用
import heapq
listForTree = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
heapq.heapify(listForTree) # for a min heap
heapq._heapify_max(listForTree) # for a maxheap!!
如果你想弹出元素,使用:
heapq.heappop(minheap) # pop from minheap
heapq._heappop_max(maxheap) # pop from maxheap
【讨论】:
看起来有一些用于最大堆的未记录函数:_heapify_max
、_heappushpop_max
、_siftdown_max
和 _siftup_max
。
哇。我惊讶在 heapq 中有 IS 这样的内置解决方案。但是在官方文档中根本没有没有提到它是完全不合理的!见鬼!
任何pop/push函数都破坏了最大堆结构,所以这种方法不可行。
不要使用它。正如 LinMa 和 Siddhartha 所注意到的,push/pop 打破了顺序。
以下划线开头的方法是私有的,可以删除,恕不另行通知。不要使用它们。【参考方案13】:
允许您选择任意数量的最大或最小项目
import heapq
heap = [23, 7, -4, 18, 23, 42, 37, 2, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
heapq.heapify(heap)
print(heapq.nlargest(3, heap)) # [42, 42, 37]
print(heapq.nsmallest(3, heap)) # [-4, -4, 2]
【讨论】:
解释一下。 我的标题是我的解释 我的回答比问题长。你想补充什么解释? wikipedia.org/wiki/Min-max_heap 和 docs.python.org/3.0/library/heapq.html 也可能会有所帮助。 这给出了正确的结果,但实际上并没有使用堆来提高效率。该文档指定 nlargest 和 nsmallest 每次对列表进行排序。【参考方案14】:我实现了 heapq 的最大堆版本并将其提交给 PyPI。 (heapq 模块 CPython 代码的微小变化。)
https://pypi.python.org/pypi/heapq_max/
https://github.com/he-zhe/heapq_max
安装
pip install heapq_max
用法
tl;dr: 与 heapq 模块相同,只是在所有函数中添加了“_max”。
heap_max = [] # creates an empty heap
heappush_max(heap_max, item) # pushes a new item on the heap
item = heappop_max(heap_max) # pops the largest item from the heap
item = heap_max[0] # largest item on the heap without popping it
heapify_max(x) # transforms list into a heap, in-place, in linear time
item = heapreplace_max(heap_max, item) # pops and returns largest item, and
# adds new item; the heap size is unchanged
【讨论】:
【参考方案15】:我创建了一个堆包装器,它反转值以创建一个最大堆,以及一个最小堆的包装器类,以使库更像 OOP。 Here 是要点。分为三类;堆(抽象类)、HeapMin 和 HeapMax。
方法:
isempty() -> bool; obvious
getroot() -> int; returns min/max
push() -> None; equivalent to heapq.heappush
pop() -> int; equivalent to heapq.heappop
view_min()/view_max() -> int; alias for getroot()
pushpop() -> int; equivalent to heapq.pushpop
【讨论】:
【参考方案16】:如果您要插入可比较但不类似于 int 的键,您可能会覆盖它们上的比较运算符(即 并且 > 变为
【讨论】:
“都是 Python 代码”:这取决于您的 Python 版本和安装。例如,我安装的 heapq.py 在第 309 行 (# If available, use C implementation
) 之后有一些代码完全符合注释的描述。【参考方案17】:
最简单的方法是反转键的值并使用 heapq。例如,将 1000.0 变为 -1000.0,将 5.0 变为 -5.0。
【讨论】:
这也是标准解决方案。 ugh;总杂乱无章。我很惊讶heapq
没有提供相反的内容。
哇。我惊讶heapq
没有提供此功能,并且没有其他好的选择。
@gatoatigrado:如果你有一些不容易映射到int
/float
的东西,你可以通过将它们包装在一个带有倒置__lt__
运算符的类中来反转排序。
如果一开始就混合了正数和负数怎么办?然后呢?以上是关于我在 Python 中使用啥来实现最大堆?的主要内容,如果未能解决你的问题,请参考以下文章