O que eu uso para uma implementação de heap máximo no Python?

227

Python inclui o módulo heapq para min-heaps, mas eu preciso de um heap máximo. O que devo usar para uma implementação de heap máximo no Python?

Douglas Mayle
fonte

Respostas:

244

A maneira mais fácil é inverter o valor das chaves e usar o heapq. Por exemplo, transforme 1000.0 em -1000.0 e 5.0 em -5.0.

Daniel Stutzbach
fonte
38
É também a solução padrão.
Andrew McGregor
44
uggh; total kludge. Estou surpreso heapqnão fornece um reverso.
shabbychef
40
Uau. Estou surpreso que isso não seja fornecido heapqe que não haja uma boa alternativa.
ire_and_curses
23
@gatoatigrado: se você tem algo que não mapeia facilmente para int/ float, pode inverter a ordem envolvendo-os em uma classe com um __lt__operador invertido .
Daniel Stutzbach
5
@Aerovistae o mesmo conselho se aplica: inverta os valores (ou seja, mude o sinal), independentemente se positivo ou negativo para começar.
Dennis
235

Você pode usar

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!!

Se você deseja exibir elementos, use:

heapq.heappop(minheap)      # pop from minheap
heapq._heappop_max(maxheap) # pop from maxheap
Lijo Joseph
fonte
34
Parece que existem algumas funções não documentadas para montão máx: _heapify_max, _heappushpop_max, _siftdown_max, e _siftup_max.
ziyuang
127
Uau. Estou espantado que há É como um built-in solução em heapq. Mas, então, é totalmente irracional que isso NÃO seja nem um pouco mencionado no documento oficial! WTF!
RayLuo
27
Qualquer uma das funções pop / push interrompe a estrutura máxima do heap, portanto, esse método não é viável.
Siddhartha
22
NÃO USE ISSO. Como LinMa e Siddhartha notaram, push / pop quebra a ordem.
Alex Fedulov
13
Os métodos que começam com um sublinhado são privados e podem ser removidos sem aviso prévio . Não os use.
precisa saber é o seguinte
66

A solução é negar seus valores quando você os armazena na pilha ou inverte a comparação de objetos da seguinte maneira:

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)

Exemplo de um heap máximo:

maxh = []
heapq.heappush(maxh, MaxHeapObj(x))
x = maxh[0].val  # fetch max value
x = heapq.heappop(maxh).val  # pop max value

Mas você deve se lembrar de agrupar e desembrulhar seus valores, o que requer saber se você está lidando com um heap mínimo ou máximo.

Classes MinHeap, MaxHeap

Adicionar classes MinHeape MaxHeapobjetos pode simplificar seu código:

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

Exemplo de uso:

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"
Isaac Turner
fonte
Agradável. Eu peguei isso e adicionei um listparâmetro opcional a __init__, nesse caso eu chamo heapq.heapifye também adicionei um heapreplacemétodo.
Booboo 30/04
1
Surpreso que ninguém tenha percebido esse erro: MaxHeapInt -> MaxHeapObj. Caso contrário, uma solução muito limpa de fato.
Chiraz BenAbdelkader
@ChirazBenAbdelkader corrigido, obrigado.
Isaac Turner
39

A solução mais fácil e ideal

Multiplique os valores por -1

Ai está. Todos os números mais altos agora são os mais baixos e vice-versa.

Lembre-se de que quando você popula um elemento para multiplicá-lo por -1, a fim de obter o valor original novamente.

Sebastian Nielsen
fonte
Ótimo, mas a maioria das soluções oferece suporte a classes / outros tipos e não altera os dados reais. A questão em aberto é se a multiplicação do valor por -1 não os mudará (flutuação extremamente precisa).
Alex Baranowski
1
@AlexBaranowski. Isso é verdade, mas essa foi a resposta do mantenedor: bugs.python.org/issue27295
Flair
Os mantenedores de poço têm o direito de não implementar algumas funcionalidades, mas este IMO é realmente útil.
Alex Baranowski
7

Eu implementei uma versão de heap max do heapq e a enviei ao PyPI. (Alteração muito pequena do código CPython do módulo heapq.)

https://pypi.python.org/pypi/heapq_max/

https://github.com/he-zhe/heapq_max

Instalação

pip install heapq_max

Uso

tl; dr: o mesmo que o módulo heapq, exceto a adição de '_max' a todas as funções.

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
Zhe He
fonte
4

Se você estiver inserindo chaves comparáveis, mas não parecidas com int, você pode potencialmente substituir os operadores de comparação nelas (por exemplo, <= torne-se> e> torna-se <=). Caso contrário, você poderá substituir o heapq._siftup no módulo heapq (no final, tudo é apenas código Python).

rlotun
fonte
9
“É tudo apenas código Python”: depende da sua versão e instalação do Python. Por exemplo, meu heapq.py instalado possui algum código após a linha 309 ( # If available, use C implementation) que faz exatamente o que o comentário descreve.
tzot 17/10/10
3

Permitindo que você escolha uma quantidade arbitrária de itens maiores ou menores

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
fonte
3
Uma explicação estaria em ordem.
Peter Mortensen
Meu título é a minha explicação
jasonleonhard
1
Minha resposta é mais longa que a pergunta. Que explicação você gostaria de adicionar?
Jasonleonhard
wikipedia.org/wiki/Min-max_heap e docs.python.org/3.0/library/heapq.html também podem ser de alguma ajuda.
Jasonleonhard
2
Isso fornece o resultado correto, mas na verdade não usa um heap para torná-lo eficiente. O documento especifica que a maior e a menor ordenam a lista todas as vezes.
RossFabricant
3

Estender a classe int e substituir __lt__ é uma das maneiras.

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
fonte
É possível, mas acho que isso atrasaria bastante as coisas e consumiria muita memória extra. O MyInt também não pode ser usado fora da estrutura da pilha. Mas obrigado por digitar um exemplo, é interessante ver.
Leo Ufimtsev
Hah! Um dia depois de comentar, me deparei com a situação em que precisava colocar um objeto personalizado em um heap e precisava de um heap máximo. Na verdade, voltei a pesquisar este post e encontrei sua resposta e baseei minha solução nela. (Sendo um ponto com x objeto sob encomenda, coordenada y e lt substituindo comparando distância do centro). Obrigado por postar isso, eu votei!
Leo Ufimtsev
1

Criei um invólucro de heap que inverte os valores para criar um heap máximo, bem como uma classe de invólucro para um min-heap para tornar a biblioteca mais parecida com OOP. Aqui está a essência. Existem três classes; Heap (classe abstrata), HeapMin e HeapMax.

Métodos:

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ɐɹƆ
fonte
0

Caso você queira obter o maior elemento K usando o max heap, execute o seguinte truque:

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
fonte
1
Infelizmente, a complexidade do tempo para isso é O (MlogM) em que M = len (nums), que anula o objetivo do heapq. Veja a implementação e os comentários nlargestaqui -> github.com/python/cpython/blob/…
Arthur S
1
Obrigado pelo seu comentário informativo, verifique o link em anexo.
RowanX
0

Seguindo a excelente resposta de Isaac Turner , gostaria de colocar um exemplo com base em K pontos mais próximos da origem usando max heap.

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)
Apoorv Patne
fonte
0

Para elaborar em https://stackoverflow.com/a/59311063/1328979 , aqui está uma implementação do Python 3 totalmente documentada, anotada e testada para o caso geral.

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

Marc Carré
fonte
0

Esta é uma MaxHeapimplementação simples baseada em heapq. Embora funcione apenas com valores numéricos.

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)

Uso:

max_heap = MaxHeap()
max_heap.push(3)
max_heap.push(5)
max_heap.push(1)
print(max_heap.top())  # 5
Yuchen Zhong
fonte