如何在Python中实现最大堆?


Answers:


241

最简单的方法是反转键的值并使用heapq。例如,将1000.0转换为-1000.0,将5.0转换为-5.0。


37
这也是标准解决方案。
Andrew McGregor 2010年

43
ugg 总的udge。我很惊讶heapq没有提供反向。
shabbychef 10-4-17的

40
哇。令我惊讶的是,它没有提供heapq,并且没有很好的选择。
ire_and_curses

23
@gatoatigrado:如果您有一些不容易映射到int/的内容float,则可以通过使用反型__lt__运算符将它们包装在一个类中来反转其顺序。
Daniel Stutzbach

5
@Aerovistae同样的建议适用:反转数值(即切换符号),无论从正数还是负数开始。
丹尼斯

233

您可以使用

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

34
貌似是最大堆一些未公开的函数:_heapify_max_heappushpop_max_siftdown_max,和_siftup_max
ziyuang 2014年

127
哇。我惊讶,有IS这样的内置在heapq解决方案。但是,在正式文件中甚至根本没有提到它完全是不合理的!WTF!
RayLuo 2015年

27
任何pop / push函数都会破坏最大堆结构,因此该方法不可行。
悉达多

22
不要使用它。正如LinMa和Siddhartha所注意到的,推/弹出打破了顺序。
Alex Fedulov '17

13
下划线开头的方法是私有的,可以删除而无需事先通知。不要使用它们。
user4815162342

66

解决方案是在将值存储在堆中时取反值,或像这样反转对象比较:

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"

真好 我已经采取了这个方法,list并向__init__ 添加了一个可选参数,在这种情况下,我调用heapq.heapify了该heapreplace方法,并且还添加了一个方法。
Booboo

1
令人惊讶的是没有人发现这种错字:MaxHeapInt-> MaxHeapObj。否则,确实是一个非常干净的解决方案。
Chiraz BenAbdelkader

@ChirazBenAbdelkader已修复,谢谢。
艾萨克·特纳

39

最简单理想的解决方案

将值乘以-1

妳去 现在,所有最高的数字都是最低的,反之亦然。

只需记住,当您弹出一个元素以使其与-1相乘时,才能再次获得原始值。


很好,但是大多数解决方案都支持类/其他类型,并且不会更改实际数据。悬而未决的问题是,将值乘以-1是否不会更改它们(极其精确的浮点数)。
Alex Baranowski

1
@AlexBaranowski。是的,但这是维护者的回应:bugs.python.org/issue27295
Flair

维护良好的人员有权不实施某些功能,但此IMO实际上是有用的。
Alex Baranowski

7

我实现了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

4

如果您插入的是可比较的但不是int的键,则可能会覆盖它们上的比较运算符(即<=变为>,而>变为<=)。否则,您可以在heapq模块中覆盖heapq._siftup(最后只是Python代码)。


9
“全都是Python代码”:这取决于您的Python版本和安装。例如,我安装的heapq.py在第309行(# If available, use C implementation)之后有一些代码,该代码完全符合注释的描述。
tzot 2010年

3

允许您选择任意数量的最大或最小项目

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]

3
一个解释将是有条理的。
Peter Mortensen

我的标题是我的解释
jasonleonhard

1
我的回答比问题更长。您想补充什么说明?
jasonleonhard


2
这样可以给出正确的结果,但是实际上并没有使用堆来提高效率。该文档指定每次最大和最小排序列表。
RossFabricant

3

扩展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也不能在堆结构之外使用。但是,感谢您输入示例,很有趣。
Leo Ufimtsev

哈哈!发表评论后的一天,我遇到了一种情况,我需要将自定义对象放入堆中,并且需要最大堆。实际上,我重新搜索了这篇文章,找到了您的答案,并以此为基础提出了解决方案。(自定义对象是一个点,具有x,y坐标,并且lt优先于距中心的距离)。感谢您发布此信息,我赞成!
Leo Ufimtsev

1

我创建了一个堆包装器,该堆包装器将这些值取反以创建一个最大堆,以及一个用于最小堆的包装器类,以使库更像OOP。这里是要点。一共有三节课;堆(抽象类),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

0

如果您想使用最大堆来获取最大的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]

1
不幸的是,这样做的时间复杂度是O(MlogM),其中M = len(nums),这违反了heapq的目的。请参阅实施和评论nlargest在这里- > github.com/python/cpython/blob/...
亚瑟小号

1
感谢您提供的有益评论,请务必检查附件中的链接。
RowanX

0

遵循艾萨克·特纳(Isaac Turner)的出色回答,我想举一个基于使用最大堆的K个最接近原点的示例。

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)

0

为了详细说明https://stackoverflow.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


0

这是MaxHeap基于的简单实现heapq。虽然它仅适用于数值。

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.