Platone Data Intelligence.
Ricerca verticale e intelligenza artificiale.

Guida agli heap in Python

Data:

Introduzione

Immagina un aeroporto affollato con voli che decollano e atterrano ogni minuto. Proprio come i controllori del traffico aereo danno priorità ai voli in base all’urgenza, gli heap ci aiutano a gestire ed elaborare i dati in base a criteri specifici, garantendo che i dati più “urgenti” o “importanti” siano sempre accessibili in alto.

In questa guida intraprenderemo un viaggio per comprendere gli heap da zero. Inizieremo demistificando cosa sono gli heap e le loro proprietà intrinseche. Da lì, approfondiremo l'implementazione degli heap di Python, the heapq modulo ed esplorare il suo ricco set di funzionalità. Quindi, se ti sei mai chiesto come gestire in modo efficiente un set dinamico di dati in cui è spesso necessario l'elemento con la priorità più alta (o più bassa), sei pronto per una sorpresa.

Cos'è un mucchio?

La prima cosa che vorresti capire prima di immergerti nell'uso degli heap è cos'è un mucchio. Un heap si distingue nel mondo delle strutture dati come una centrale elettrica ad albero, particolarmente abile mantenimento dell'ordine e della gerarchia. Anche se ad un occhio inesperto potrebbe somigliare ad un albero binario, le sfumature nella sua struttura e nelle regole che lo governano lo distinguono nettamente.

Una delle caratteristiche distintive di un heap è la sua natura come a albero binario completo. Ciò significa che ogni livello dell'albero, tranne forse l'ultimo, è interamente riempito. All'interno di quest'ultimo livello, i nodi si popolano da sinistra a destra. Una struttura di questo tipo garantisce che gli heap possano essere rappresentati e manipolati in modo efficiente utilizzando array o elenchi, con la posizione di ciascun elemento nell'array che rispecchia la sua posizione nell'albero.

guide-to-heaps-in-python-01.png

La vera essenza di un mucchio, tuttavia, sta nella sua ordinazione. In un heap max, il valore di ogni dato nodo supera o eguaglia i valori dei suoi figli, posizionando l'elemento più grande proprio alla radice. D'altra parte, a min mucchio funziona secondo il principio opposto: il valore di qualsiasi nodo è inferiore o uguale ai valori dei suoi figli, garantendo che l'elemento più piccolo si trovi alla radice.

guide-to-heaps-in-python-02.png

Consigli: Puoi visualizzare un heap come a piramide di numeri. Per un heap massimo, man mano che si sale dalla base al picco, i numeri aumentano, culminando nel valore massimo in cima. Al contrario, un heap minimo inizia con il valore minimo nel suo picco, con numeri che aumentano man mano che ci si sposta verso il basso.

Man mano che progrediamo, approfondiremo il modo in cui queste proprietà intrinseche degli heap consentono operazioni efficienti e come quelle di Python heapq Il modulo integra perfettamente gli heap nelle nostre attività di codifica.

Caratteristiche e proprietà degli heap

Gli heap, con la loro struttura unica e i loro principi di ordinamento, producono una serie di caratteristiche e proprietà distinte che li rendono preziosi in vari scenari computazionali.

Innanzitutto, i cumuli lo sono intrinsecamente efficiente. La loro struttura ad albero, in particolare il formato ad albero binario completo, garantisce che operazioni come l'inserimento e l'estrazione degli elementi prioritari (massimo o minimo) possano essere eseguite in tempo logaritmico, tipicamente O (log n). Questa efficienza è un vantaggio per gli algoritmi e le applicazioni che richiedono un accesso frequente a elementi prioritari.

Un'altra proprietà notevole degli heap è la loro efficienza della memoria. Poiché gli heap possono essere rappresentati utilizzando array o elenchi senza la necessità di puntatori espliciti ai nodi figlio o padre, consentono di risparmiare spazio. La posizione di ciascun elemento nell'array corrisponde alla sua posizione nell'albero, consentendo un attraversamento e una manipolazione prevedibili e diretti.

La proprietà di ordinamento degli heap, sia come heap massimo che come heap minimo, lo garantisce la radice contiene sempre l'elemento con la massima priorità. Questo ordinamento coerente è ciò che consente un rapido accesso all'elemento con la massima priorità senza dover cercare nell'intera struttura.

Inoltre, i cumuli lo sono versatile. Mentre gli heap binari (dove ogni genitore ha al massimo due figli) sono i più comuni, gli heap possono essere generalizzati per avere più di due figli, noti come d-ario cumuli. Questa flessibilità consente la regolazione fine in base a casi d'uso specifici e requisiti prestazionali.

Infine, i cumuli lo sono autoregolante. Ogni volta che vengono aggiunti o rimossi elementi, la struttura si riorganizza per mantenere le sue proprietà. Questo bilanciamento dinamico garantisce che l'heap rimanga sempre ottimizzato per le operazioni principali.

Consigli: Queste proprietà hanno reso la struttura dei dati dell'heap una buona soluzione per un algoritmo di ordinamento efficiente: l'ordinamento dell'heap. Per saperne di più sull'ordinamento dell'heap in Python, leggi il nostro “Ordinamento heap in Python” articolo.

Man mano che approfondiamo l'implementazione e le applicazioni pratiche di Python, il vero potenziale degli heap si svelerà davanti a noi.

Tipi di cumuli

Non tutti gli heap sono uguali. A seconda del loro ordinamento e delle proprietà strutturali, gli heap possono essere classificati in diversi tipi, ciascuno con il proprio insieme di applicazioni e vantaggi. Le due categorie principali sono heap max ed min mucchio.

La caratteristica più distintiva di a heap max è che il valore di ogni dato nodo è maggiore o uguale ai valori dei suoi figli. Ciò garantisce che l'elemento più grande nell'heap risieda sempre nella radice. Tale struttura è particolarmente utile quando è necessario accedere frequentemente all'elemento massimo, come in alcune implementazioni della coda con priorità.

La controparte dell'heap massimo, a min mucchio garantisce che il valore di ogni dato nodo sia inferiore o uguale ai valori dei suoi figli. Ciò posiziona l'elemento più piccolo dell'heap alla radice. Gli heap minimi hanno un valore inestimabile negli scenari in cui l'elemento minimo è di primaria importanza, come negli algoritmi che si occupano dell'elaborazione dei dati in tempo reale.

Oltre a queste categorie primarie, gli heap possono essere distinti anche in base al loro fattore di ramificazione:

Sebbene gli heap binari siano i più comuni, in cui ciascun genitore ha al massimo due figli, il concetto di heap può essere esteso ai nodi che hanno più di due figli. In un mucchio d-ario, ogni nodo ha al massimo d bambini. Questa variazione può essere ottimizzata per scenari specifici, come diminuire l'altezza dell'albero per accelerare determinate operazioni.

Binomiale Heap è un insieme di alberi binomiali definiti ricorsivamente. Gli heap binomiali vengono utilizzati nelle implementazioni delle code con priorità e offrono operazioni di unione efficienti.

Prende il nome dalla famosa sequenza di Fibonacci, la Mucchio di Fibonacci offre tempi di esecuzione meglio ammortizzati per molte operazioni rispetto agli heap binari o binomiali. Sono particolarmente utili negli algoritmi di ottimizzazione della rete.

Implementazione dell'heap di Python: il heapq Moduli

Python offre un modulo integrato per le operazioni sull'heap: il heapq modulo. Questo modulo fornisce una raccolta di funzioni relative all'heap che consentono agli sviluppatori di trasformare elenchi in heap ed eseguire varie operazioni sull'heap senza la necessità di un'implementazione personalizzata. Immergiamoci nelle sfumature di questo modulo e in che modo ti offre la potenza degli heap.

Il heapq il modulo non fornisce un tipo di dati heap distinto. Offre invece funzioni che funzionano su normali elenchi Python, trasformandoli e trattandoli come cumuli binari.

Questo approccio è efficiente in termini di memoria e si integra perfettamente con le strutture dati esistenti di Python.

Ciò significa che gli heap sono rappresentati come elenchi in heapq. La bellezza di questa rappresentazione è la sua semplicità: il sistema di indici di elenco a base zero funge da albero binario implicito. Per ogni dato elemento in posizione i, suo:

  • Il bambino sinistro è in posizione 2*i + 1
  • Il bambino destro è in posizione 2*i + 2
  • Il nodo genitore è in posizione (i-1)//2

guide-to-heaps-in-python-03.png

Questa struttura implicita garantisce che non sia necessaria una rappresentazione separata dell'albero binario basata su nodi, rendendo le operazioni semplici e l'utilizzo della memoria minimo.

Complessità spaziale: Gli heap vengono in genere implementati come alberi binari ma non richiedono l'archiviazione di puntatori espliciti per i nodi figlio. Ciò li rende efficienti in termini di spazio con una complessità spaziale di O (n) per memorizzare n elementi.

È essenziale notare che il heapq modulo crea heap minimi per impostazione predefinita. Ciò significa che l'elemento più piccolo è sempre alla radice (o alla prima posizione nell'elenco). Se hai bisogno di un heap massimo, dovresti invertire l'ordine moltiplicando gli elementi per -1 o utilizzare una funzione di confronto personalizzata.

Python's heapq Il modulo fornisce una suite di funzioni che consentono agli sviluppatori di eseguire varie operazioni heap sugli elenchi.

Nota: Per utilizzare l' heapq module nella tua applicazione, dovrai importarlo utilizzando simple import heapq.

Nelle sezioni seguenti, approfondiremo ciascuna di queste operazioni fondamentali, esplorandone i meccanismi e i casi d'uso.

Come trasformare una lista in un heap

Il heapify() La funzione è il punto di partenza per molte attività relative all'heap. Richiede un elemento iterabile (tipicamente un elenco) e riorganizza i suoi elementi sul posto per soddisfare le proprietà di un heap minimo:

Dai un'occhiata alla nostra guida pratica e pratica per l'apprendimento di Git, con le migliori pratiche, gli standard accettati dal settore e il cheat sheet incluso. Smetti di cercare su Google i comandi Git e in realtà imparare esso!

import heapq data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
heapq.heapify(data)
print(data)

Ciò genererà un elenco riordinato che rappresenta un heap minimo valido:

[1, 1, 2, 3, 3, 9, 4, 6, 5, 5, 5]

Complessità temporale: Convertire un elenco non ordinato in un heap utilizzando il metodo heapify la funzione è an O (n) operazione. Ciò potrebbe sembrare controintuitivo, come ci si potrebbe aspettare O (nlogn), ma grazie alle proprietà della struttura ad albero, può essere ottenuto in tempo lineare.

Come aggiungere un elemento all'heap

Il heappush() la funzione ti consente di inserire un nuovo elemento nell'heap mantenendo le proprietà dell'heap:

import heapq heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 3)
heapq.heappush(heap, 7)
print(heap)

L'esecuzione del codice ti fornirà un elenco di elementi che mantengono la proprietà min heap:

[3, 5, 7]

Complessità temporale: L'operazione di inserimento in un heap, che prevede l'inserimento di un nuovo elemento nell'heap mantenendo la proprietà dell'heap, ha una complessità temporale di O (log). Questo perché, nel peggiore dei casi, l'elemento potrebbe dover viaggiare dalla foglia alla radice.

Come rimuovere e restituire l'elemento più piccolo dall'heap

Il heappop() la funzione estrae e restituisce l'elemento più piccolo dall'heap (la radice in un heap minimo). Dopo la rimozione, garantisce che l'elenco rimanga un heap valido:

import heapq heap = [1, 3, 5, 7, 9]
print(heapq.heappop(heap))
print(heap)

Nota: Il heappop() ha un valore inestimabile negli algoritmi che richiedono l'elaborazione degli elementi in ordine crescente, come l'algoritmo Heap Sort, o quando si implementano code con priorità in cui le attività vengono eseguite in base alla loro urgenza.

Questo produrrà l'elemento più piccolo e l'elenco rimanente:

1
[3, 7, 5, 9]

Qui, 1 è l'elemento più piccolo di heape l'elenco rimanente ha mantenuto la proprietà heap, anche dopo la rimozione 1.

Complessità temporale: Anche la rimozione dell'elemento root (che è il più piccolo in un heap minimo o il più grande in un heap massimo) e la riorganizzazione dell'heap richiedono O (log) tempo.

Come spingere un nuovo oggetto e far apparire l'oggetto più piccolo

Il heappushpop() La funzione è un'operazione combinata che inserisce un nuovo elemento nell'heap, quindi lo apre e restituisce l'elemento più piccolo dall'heap:

import heapq heap = [3, 5, 7, 9]
print(heapq.heappushpop(heap, 4)) print(heap)

Questo uscirà 3, l'elemento più piccolo, e stampa il nuovo heap elenco che ora include 4 mantenendo la proprietà heap:

3
[4, 5, 7, 9]

Nota: Usando il heappushpop() La funzione è più efficiente dell'esecuzione di operazioni di inserimento di un nuovo elemento e di estrazione separata di quello più piccolo.

Come sostituire l'elemento più piccolo e inserire un nuovo elemento

Il heapreplace() la funzione fa apparire l'elemento più piccolo e inserisce un nuovo elemento nell'heap, il tutto in un'unica operazione efficiente:

import heapq heap = [1, 5, 7, 9]
print(heapq.heapreplace(heap, 4))
print(heap)

Questo stampa 1, l'elemento più piccolo, e l'elenco ora ne include 4 e mantiene la proprietà heap:

1
[4, 5, 7, 9]

Note:: heapreplace() è utile negli scenari di streaming in cui si desidera sostituire l'elemento più piccolo corrente con un nuovo valore, ad esempio nelle operazioni di finestra mobile o nelle attività di elaborazione dei dati in tempo reale.

Trovare più estremi nell'heap di Python

nlargest(n, iterable[, key]) ed nsmallest(n, iterable[, key]) le funzioni sono progettate per recuperare più elementi più grandi o più piccoli da un iterabile. Possono essere più efficienti dell'ordinamento dell'intero iterabile quando sono necessari solo pochi valori estremi. Ad esempio, supponiamo di avere il seguente elenco e di voler trovare tre valori più piccoli e tre valori più grandi nell'elenco:

data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

Qui, nlargest() ed nsmallest() le funzioni possono tornare utili:

import heapq data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(heapq.nlargest(3, data)) print(heapq.nsmallest(3, data)) 

Questo ti darà due elenchi: uno contiene i tre valori più grandi e l'altro contiene i tre valori più piccoli da data elenco:

[9, 6, 5]
[1, 1, 2]

Come costruire il tuo heap personalizzato

Mentre quello di Python heapq Il modulo fornisce un insieme affidabile di strumenti per lavorare con gli heap, esistono scenari in cui il comportamento predefinito dell'heap minimo potrebbe non essere sufficiente. Se stai cercando di implementare un heap massimo o hai bisogno di un heap che funzioni in base a funzioni di confronto personalizzate, la creazione di un heap personalizzato può essere la risposta. Esploriamo come adattare gli heap a esigenze specifiche.

Implementazione di un heap massimo utilizzando heapq

Per impostazione predefinita, heapq crea cumuli minimi. Tuttavia, con un semplice trucco, puoi usarlo per implementare un heap massimo. L'idea è di invertire l'ordine degli elementi moltiplicandoli per -1 prima di aggiungerli all'heap:

import heapq class MaxHeap: def __init__(self): self.heap = [] def push(self, val): heapq.heappush(self.heap, -val) def pop(self): return -heapq.heappop(self.heap) def peek(self): return -self.heap[0]

Con questo approccio, il numero più grande (in termini di valore assoluto) diventa il più piccolo, consentendo il heapq funzioni per mantenere una struttura heap massima.

Heap con funzioni di confronto personalizzate

A volte, potresti aver bisogno di un heap che non si confronti solo in base all'ordine naturale degli elementi. Ad esempio, se lavori con oggetti complessi o hai criteri di ordinamento specifici, una funzione di confronto personalizzata diventa essenziale.

Per raggiungere questo obiettivo, puoi racchiudere gli elementi in una classe helper che sovrascrive gli operatori di confronto:

import heapq class CustomElement: def __init__(self, obj, comparator): self.obj = obj self.comparator = comparator def __lt__(self, other): return self.comparator(self.obj, other.obj) def custom_heappush(heap, obj, comparator=lambda x, y: x < y): heapq.heappush(heap, CustomElement(obj, comparator)) def custom_heappop(heap): return heapq.heappop(heap).obj

Con questa configurazione è possibile definire qualsiasi funzione di comparazione personalizzata e utilizzarla con l'heap.

Conclusione

Gli heap offrono prestazioni prevedibili per molte operazioni, rendendoli una scelta affidabile per attività basate sulle priorità. Tuttavia, è essenziale considerare i requisiti e le caratteristiche specifici dell'applicazione in questione. In alcuni casi, modificare l'implementazione dell'heap o addirittura optare per strutture dati alternative potrebbe produrre prestazioni migliori nel mondo reale.

Gli heap, come abbiamo visto, sono più di una semplice struttura di dati. Rappresentano una confluenza di efficienza, struttura e adattabilità. Dalle loro proprietà fondamentali alla loro implementazione in Python heapq modulo, gli heap offrono una soluzione solida a una miriade di sfide computazionali, in particolare quelle incentrate sulla priorità.

spot_img

L'ultima intelligenza

spot_img

Parla con noi

Ciao! Come posso aiutarla?