heapq - Algoritmo di Ordinamento Heapsort
Scopo: Implementa un algoritmo di ordinamento min-heap adatto all'uso con le liste Python.
Un heap è una struttura dati di tipo ad albero nella quale i nodi figli hanno una relazione di ordinamento con i genitori. Gli heap binari possono essere rappresentati usando una lista oppure un array organizzato in modo che i figli dell'elemento N
siano alle posizioni 2 * N + 1
e 2 * N + 2
(per indici a base zero). Questa disposizione rende possibile riordinare gli elementi sul posto, in modo che non sia necessario riallocare tanta memoria quanta ne servirebbe per aggiungere o rimuovere elementi.
Un max-heap assicura che il genitore sia più grande od uguale ad entrambi i figli. Un min-heap richiede che il genitore sia minore od uguale ai suoi figli. Il modulo heapq di Python implementa min-heap.
Dati di Esempio
Gli esempi in questa sezione utilizzano i dati in heapq_heapdata.py
.
# heapq_heapdata.py
# Questi dati osono stati generati con il modulo random.
data = [19, 9, 4, 10, 11]
Il risultato è stampato usando heapq_showtree.py
.
# heapq_showtree.py
import math
from io import StringIO
def show_tree(tree, total_width=36, fill=' '):
"""Stampa formattata di un albero."""
output = StringIO()
last_row = -1
for i, n in enumerate(tree):
if i:
row = int(math.floor(math.log(i + 1, 2)))
else:
row = 0
if row != last_row:
output.write('\n')
columns = 2 ** row
col_width = int(math.floor(total_width / columns))
output.write(str(n).center(col_width, fill))
last_row = row
print(output.getvalue())
print('-' * total_width)
print()
Creare un Heap
Ci sono due metodi base per creare un heap: heappush()
ed heapify()
.
# heapq_heappush.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heap = []
print('casuali :', data)
print()
for n in data:
print('aggiungo {:>3}:'.format(n))
heapq.heappush(heap, n)
show_tree(heap)
Quando viene usato heappush()
, il tipo di ordinamento dell'heap viene mantenuto mentre vengono aggiunti nuovi elementi alla sorgente dati.
$ python3 heapq_heappush.py casuali : [19, 9, 4, 10, 11] aggiungo 19: 19 ------------------------------------ aggiungo 9: 9 19 ------------------------------------ aggiungo 4: 4 19 9 ------------------------------------ aggiungo 10: 4 10 9 19 ------------------------------------ aggiungo 11: 4 10 9 19 11 ------------------------------------
Se i dati sono già in memoria, è più efficace l'uso di heapify()
per ricollocare gli elementi della lista sul posto.
# heapq_heapify.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('casuali :', data)
heapq.heapify(data)
print('con heap:')
show_tree(data)
La costruzione di una lista con l'ordinamento heap un elemento alla volta equivale alla costruzione di una lista non ordinata e la successiva chiamata di heapify()
.
$ python3 heapq_heapify.py casuali : [19, 9, 4, 10, 11] con heap: 4 9 19 10 11 ------------------------------------
Accedere ai Contenuti di un Heap
Una volta che l'heap viene organizzato correttamente, si usi heappop()
per rimuovere l'elemento con il valore più basso.
# heapq_heappop.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('casuale :', data)
heapq.heapify(data)
print('con heap:')
show_tree(data)
print()
for i in range(2):
smallest = heapq.heappop(data)
print('rimosso {:>3}:'.format(smallest))
show_tree(data)
In questo esempio, adattato dalla documentazione della libreria standard, heapify()
ed heappop()
sono usati per ordinare una lista di numeri.
$ python3 heapq_heappop.py casuale : [19, 9, 4, 10, 11] con heap: 4 9 19 10 11 ------------------------------------ rimosso 4: 9 10 19 11 ------------------------------------ rimosso 9: 10 11 19 ------------------------------------
Per rimuovere elementi esistenti e sostituirli con nuovi valori in una singola operazione, si usi heapreplace()
.
# heapq_heapreplace.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heapq.heapify(data)
print('inizio:')
show_tree(data)
for n in [0, 13]:
smallest = heapq.heapreplace(data, n)
print('sostituzione di {:>2} con {:>2}:'.format(smallest, n))
show_tree(data)
La sostituzione di elementi sul posto rende possibile il mantenimento di un heap a dimensione fissa, come ad esempio una lista di lavorazioni ordinate per priorità.
$ python3 heapq_heapreplace.py inizio: 4 9 19 10 11 ------------------------------------ sostituzione di 4 con 0: 0 9 19 10 11 ------------------------------------ sostituzione di 0 con 13: 9 10 19 13 11 ------------------------------------
Estremi dei Dati da un Heap
heapq include due funzioni per esaminare un oggetto iterabile e trovare l'intervallo tra il valore più piccolo e quello più grande in esso contenuti.
# heapq_extremes.py
import heapq
from heapq_heapdata import data
print('tutti :', data)
print('3 più grandi :', heapq.nlargest(3, data))
print('da ordinamento :', list(reversed(sorted(data)[-3:])))
print('3 più piccoli :', heapq.nsmallest(3, data))
print('da ordinamento :', sorted(data)[:3])
L'uso di nlargest()
e nsmallest()
per trovare il valore rispettivamente più grande e più piccolo è efficace solo in presenza di valori relativamente piccoli di n > 1
, ma in alcuni casi può essere utile.
$ python3 heapq_extremes.py tutti : [19, 9, 4, 10, 11] 3 più grandi : [19, 11, 10] da ordinamento : [19, 11, 10] 3 più piccoli : [4, 9, 10] da ordinamento : [4, 9, 10]
Combinare con Efficacia Sequenze Ordinate
La combinazione di parecchie sequenze ordinate in una nuova sequenza è facile per piccoli insiemi di dati.
list(sorted(itertools.chain(*data)))
Per insiemi di dati più grandi, questa tecnica può richiedere l'uso di un ammontare di memoria considerevole. Invece di ordinare l'intera sequenza combinata, merge()
usa un heap per generare una nuova sequenza un elemento alla volta, determinando il successivo usando un ammontare di memoria fisso.
# heapq_merge.py
import heapq
import random
random.seed(2016)
data = []
for i in range(4):
new_data = list(random.sample(range(1, 101), 5))
new_data.sort()
data.append(new_data)
for i, d in enumerate(data):
print('{}: {}'.format(i, d))
print('\nCombinati:')
for i in heapq.merge(*data):
print(i, end=' ')
print()
Visto che l'implementazione di merge()
usa un heap, consuma memoria in base al numero di sequenze che si devono combinare, invece che in base al numero di elementi con i quali sono composte dette sequenze.
$ python3 heapq_merge.py 0: [33, 58, 71, 88, 95] 1: [10, 11, 17, 38, 91] 2: [13, 18, 39, 61, 63] 3: [20, 27, 31, 42, 45] Combinati: 10 11 13 17 18 20 27 31 33 38 39 42 45 58 61 63 71 88 91 95
Vedere anche:
- heapq
- La documentazione della libreria standard per questo modulo.
- Heapsort
- Descrizione generale delle strutture dati heap
- Coda di Priorità
- Una implementazione di una coda di priorità dal modulo Queue nella libreria standard.