Esercizio 14k-esimo elemento più piccolo con uno heap
In questa pagina 6
Testo (esempio di tema d'esame, seconda parte, esercizio 1, 5 punti). Progettare un algoritmo che calcola il -esimo elemento più piccolo di un insieme di interi distinti, in tempo e senza ricorrere all'ordinamento.
Idea
Il termine suggerisce una costruzione bottom-up di uno heap, che costa ; il termine suggerisce operazioni di removeMin, ciascuna . Il minimo estratto per -esimo è il -esimo più piccolo (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap → e Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →). Ordinare tutto l'insieme costerebbe , peggio quando è piccolo (ad esempio per la mediana si hanno ; per il minimo : basta una scansione, ). È di fatto un heap sort fermato dopo passi (Algoritmi di ordinamentoSelection sort e insertion sort (quadratici, con invarianti), merge sort (divide et impera, Theta(n log n)), quick sort (caso pessimo quadratico, medio n log n), heap sort (in loco, Theta(n log n)), ordinamento senza confronti per chiavi intere in un intervallo piccolo, limite inferiore Omega(n log n) per gli algoritmi basati su confronti.Algoritmi di ordinamento →).
Algoritmo
Algoritmo kEsimoMinimo(A, n, k)
Input: array A[1..n] di interi distinti, intero 1 <= k <= n
Output: il k-esimo elemento più piccolo di A
P <- A (lavoro in loco sull'array)
for j <- floor(n/2) downto 1 do (costruzione bottom-up di un min-heap)
down-heap bubbling a partire da P[j]
last <- n
for t <- 1 to k - 1 do
P[1] <- P[last]; last <- last - 1 (removeMin, scartando il minimo)
down-heap bubbling a partire da P[1]
return P[1] (il k-esimo minimo: la radice)L'ultima operazione non serve completarla: dopo rimozioni la radice dello heap è il -esimo più piccolo (equivalentemente, si può fare removeMin() volte e restituire l'ultimo valore estratto).
Correttezza. Lo heap contiene sempre gli elementi non ancora scartati. Dopo la costruzione la radice è il minimo (1° più piccolo). A ogni removeMin si toglie il minimo corrente, quindi dopo rimozioni sono stati tolti i elementi più piccoli e la radice è il -esimo. Dopo rimozioni la radice è il -esimo.
Esempio
, .
- Bottom-up (): : ; : ; : (il scende prima scambiandosi con , poi con ).
- Rimozione 1 (si scarta ): in radice, .
- Rimozione 2 (si scarta ): in radice, .
- La radice è : il terzo più piccolo, in accordo con l'ordinamento .
Complessità
- Costruzione bottom-up: (somma dei costi per livello, vedi Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →).
- rimozioni: ciascuna (altezza dello heap ): .
Totale: . Per è lineare (perché allora ). Per confronto con l'ordinamento () ecco i numeri per :
Grafico interattivo: Costo per trovare il k-esimo minimo con n = 1000: heap n + k·log₂n contro ordinamento completo n·log₂n (≈ 9970); l'heap vince fino a k ≈ 900
Per l'heap fa circa operazioni contro dell'ordinamento. Il punto di incrocio, dove , è . Se è vicino a non conviene rispetto all'ordinamento ( in entrambi i casi), ma la richiesta dell'esercizio è proprio questo limite.
Varianti
Con una coda con priorità su array non ordinato la costruzione è banale ma ogni removeMin costa : . Con lista ordinata si paga per ordinare. Lo heap bilancia i costi (vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →).
Errori comuni
- Costruire lo heap con
insert(top-down): , che viola il limite richiesto. - Errore di uno: se si fanno estrazioni e si restituisce la radice dopo l'ultima, si ottiene il -esimo; vale o il valore estratto alla -esima estrazione, o la radice dopo estrazioni.
- Usare un max-heap senza adattare (con un max-heap si cerca il -esimo più grande).
- Dimenticare che gli elementi sono distinti: con ripetizioni "-esimo più piccolo" va ridefinito.
Versione ripasso
Testo. -esimo elemento più piccolo di interi distinti in , senza ordinare.
- Idea: ⇒ heap bottom-up; ⇒
removeMin(vedi Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →). - Algoritmo: min-heap bottom-up in ; rimozioni del minimo (, , down-heap); la radice è il -esimo minimo.
- Esempio: , : heap ; dopo due rimozioni la radice è .
- Complessità: (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →).
- Codice:
for j <- floor(n/2) downto 1: down-heap(P, j); ripeti volteP[1] <- P[last]; last--; down-heap(P, 1);return P[1]. Esempio : heap , dopo rimozioni la radice è . - Errori: costruzione con
insert(); sbagliare di uno; max-heap per il -esimo minimo.