Что я использую для реализации max-heap в Python?

Ответы:

244

Самый простой способ - инвертировать значение ключей и использовать heapq. Например, поверните 1000.0 в -1000.0 и 5.0 в -5.0.

Даниэль Штутцбах
источник
38
Это также стандартное решение.
Эндрю МакГрегор
44
uggh; общий кладж. Я удивлен heapq, не обеспечивает обратное.
Шаббычеф
40
Вот это да. Я поражен, что это не предусмотрено heapq, и что нет хорошей альтернативы.
ire_and_curses
23
@gatoatigrado: Если у вас есть что-то, что не легко сопоставить с int/ float, вы можете инвертировать порядок, заключив их в класс с инвертированным __lt__оператором.
Даниэль Штутцбах
5
Применяется тот же совет @Aerovistae: инвертировать значения (т.е. поменять знак) независимо от того, положительный или отрицательный для начала.
Деннис
235

Ты можешь использовать

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.
Цзыюан
127
Вот это да. Я поражен , что IS такой встроенный в растворе heapq. Но тогда это совершенно неразумно , что она НЕ даже слегка упоминается в официальном документе! WTF!
RayLuo
27
Любая из функций pop / push нарушает структуру максимальной кучи, поэтому этот метод невозможен.
Сиддхартха
22
НЕ ИСПОЛЬЗУЙТЕ ЕГО. Как заметили Линма и Сиддхартха, push / pop нарушает порядок.
Алекс Федулов,
14
Методы, начинающиеся с подчеркивания, являются закрытыми и могут быть удалены без предварительного уведомления . Не используйте их.
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. В противном случае, действительно очень чистое решение.
Хираз Бен Абделькадер
@ChirazBenAbdelkader исправлено, спасибо.
Исаак Тернер
40

Самое простое и идеальное решение

Умножьте значения на -1

Вот и ты. Все самые высокие цифры теперь самые низкие и наоборот.

Просто запомните это, когда вы выталкиваете элемент, чтобы умножить его на -1, чтобы снова получить исходное значение.

Себастьян Нильсен
источник
Отлично, но большинство решений поддерживает классы / другие типы и не изменяет фактические данные. Открытый вопрос: не умножит ли их значение на -1 (очень точное значение с плавающей точкой).
Алекс Барановский
1
@AlexBaranowski. Это правда, но это был ответ от сопровождающего: bugs.python.org/issue27295
Flair
Хорошо, сопровождающие имеют право не реализовывать некоторые функциональные возможности, но этот IMO действительно полезен.
Алекс Барановский
7

Я реализовал версию heapq с максимальной кучей и отправил ее в PyPI. (Очень небольшое изменение кода CPython модуля heapq.)

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._siftup в модуле heapq (в конце концов, это всего лишь код Python).

rlotun
источник
9
«Это всего лишь код Python»: это зависит от вашей версии и установки Python. Например, мой установленный heapq.py содержит некоторый код после строки 309 ( # If available, use C implementation), который делает именно то, что описывает комментарий.
Цот
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]
jasonleonhard
источник
3
Объяснение будет в порядке.
Питер Мортенсен,
Мое название - мое объяснение
jasonleonhard
1
Мой ответ длиннее вопроса. Какое объяснение вы хотели бы добавить?
Джейсон Леонард
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()
Gaurav
источник
Это возможно, но я чувствую, что это сильно замедлит работу и потребует много дополнительной памяти. MyInt также не может быть использован вне структуры кучи. Но спасибо, что напечатали пример, интересно посмотреть.
Лев Уфимцев
Хах! Однажды после того, как я прокомментировал, я столкнулся с ситуацией, когда мне нужно было поместить пользовательский объект в кучу и потребовалась максимальная куча. Я на самом деле пересмотрел этот пост, нашел ваш ответ и основал на нем свое решение. (Пользовательский объект - это точка с координатами x, y и переопределением lt при сравнении расстояния от центра). Спасибо за публикацию, я проголосовал!
Лев Уфимцев
1

Я создал оболочку кучи, которая инвертирует значения, чтобы создать максимальную кучу, а также класс оболочки для минимальной кучи, чтобы сделать библиотеку более ООП-подобной. Вот суть. Есть три класса; Heap (абстрактный класс), 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
noɥʇʎԀʎzɐɹƆ
источник
0

В случае, если вы хотите получить максимальный элемент K, используя max heap, вы можете сделать следующий трюк:

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]
RowanX
источник
1
К сожалению, сложность времени для этого составляет O (MlogM), где M = len (nums), что отрицательно сказывается на назначении heapq. Смотрите реализацию и комментарии nlargestздесь -> github.com/python/cpython/blob/…
Артур С.
1
Спасибо за ваш информативный комментарий, обязательно проверьте прикрепленную ссылку.
RowanX
0

Следуя прекрасному ответу Исаака Тернера , я бы хотел привести пример, основанный на 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
Ючен Чжун
источник