Ordinamento: generalità Spesso si tratta di ordinare non già dei singoli valori ma dei record in base al valore assunto da un campo chiave → il metodo usato per effettuare l’ordinamento non cambia ma è più efficiente permutare un array di puntatori ai record piuttosto che i record stessi Marina Zanella – Algoritmi e strutture dati – Ordinamento 1 Heap Attenzione! Qui heap è una struttura dati contenuta in un array, non una zona di memoria gestita da un garbage-collector Un array A che rappresenta uno heap è un oggetto con due attributi: • length[A] = n° elementi di A • heap-size[A] = n° elementi dello heap, memorizzati in A[1 .. heap-size[A]] dove heap-size[A] ≤ length[A] Marina Zanella – Algoritmi e strutture dati – Ordinamento 2 Heap binario Albero binario riempito completamente su tutti i livelli tranne, eventualmente, il più basso che è riempito da sx in poi radice dell’albero = elemento A[1] nodo dell’albero = elemento del sottoarray A[1 .. heap-size[A]] Sussiste il vincolo che il valore del genitore è sempre ≥ di quello del figlio (proprietà dello heap, vedi oltre) → il valore massimo dello heap è memorizzato nella radice Marina Zanella – Algoritmi e strutture dati – Ordinamento 3 Esempio: heap binario Esempio 1 2 3 4 5 6 7 8 9 10 11 12 A = <16,14,10,8,7,9,3,2,4, 1, 5,15> heap-size[A] = 10 1 16 4 2 3 14 10 5 8 7 8 7 6 1 9 3 9 10 2 4 1 Marina Zanella – Algoritmi e strutture dati – Ordinamento 4 Heap binario: operazioni PARENT(i) restituisce l’indice del padre del nodo di indice i return i/2 realizzabile come macro con lo scorrimento della codifica binaria di i di una posizione verso dx e aggiunta di 0 come bit più significativo Proprietà (di ordinamento parziale) dello heap: ∀i >1, A[PARENT(i)] ≥ A[i] Marina Zanella – Algoritmi e strutture dati – Ordinamento 5 Heap binario: operazioni (cont.) RIGHT(i) restituisce l’indice del figlio destro return 2i+1 realizzabile come macro con lo scorrimento della codifica binaria di i di una posizione verso sx e aggiunta di 1 come bit meno significativo LEFT(i) restituisce l’indice del figlio sinistro return 2i realizzabile come macro con lo scorrimento della codifica binaria di i di una posizione verso sx e aggiunta di 0 come bit meno significativo Marina Zanella – Algoritmi e strutture dati – Ordinamento 6 Heap: altezza Altezza di un nodo di un albero = n° di archi sul più lungo cammino discendente che va dal nodo a una foglia Altezza di un albero = altezza della radice dell’albero ↓ Altezza di uno heap binario di n elementi = lgn = Θ( lgn) Le operazioni di base sugli heap hanno un tempo di esecuzione al più proporzionale all’altezza dell’albero e quindi impiegano un tempo dell’ordine di O(lgn) Marina Zanella – Algoritmi e strutture dati – Ordinamento 7 HEAPIFY Procedura che acquisisce in ingresso un array A e un indice i di tale array, dove si assume che gli alberi binari con radice in LEFT(i) e RIGHT(i) siano heap ma A[i] possa essere più piccolo dei suoi figli → A[i] viene fatto “scendere” in modo tale che il sottoalbero con radice in A[i] diventi uno heap Dimensione dell’input = n° elementi del sottoalbero con radice in A[i] contenuto nelle prime heap-size[A] posizioni di A Marina Zanella – Algoritmi e strutture dati – Ordinamento 8 HEAPIFY (cont.) HEAPIFY(A, i) 1 l ← LEFT(i) 2 r ← RIGHT(i) 3 if l ≤ heap-size[A] e A[l] > A[i] 4 then largest ← l 5 else largest ← i 6 if r ≤ heap-size[A] e A[r] > A[largest] 7 then largest ← r 8 in largest è ora memorizzato d’indice del maggiore fra A[i], A[LEFT(i)] e A[RIGHT(i)] 9 if largest ≠ i 10 then scambia A[i] ↔ A[largest] 11 HEAPIFY(A, largest) Θ(1) T(2n/3) 2n/3 = caso pessimo, dimensione max del sottoalbero (si verifica quando l’ultimo livello dell’albero è pieno esattamente a metà) Marina Zanella – Algoritmi e strutture dati – Ordinamento 9 HEAPIFY (cont.) Ricorrenza: T(n) ≤ T(2n/3) + Θ(1) ↓ Soluzione: T(n) = O(lgn) (ovvero, T(n) = O(h) su un nodo di altezza h) Marina Zanella – Algoritmi e strutture dati – Ordinamento 10 1 Esempio: HEAPIFY(A, 2) 16 2 3 1 i 4 4 10 14 7 8 7 6 1 5 3 9 9 4 10 2 8 1 2 3 14 10 7 7 8 7 6 1 9 10 4 10 7 6 1 3 9 10 9 2 14 9 2 5 8 3 5 8 16 8 2 4 1 4 16 1 Marina Zanella – Algoritmi e strutture dati – Ordinamento 1 Chiamata ricorsiva di HEAPIFY(A, 4) 3 Chiamata ricorsiva di HEAPIFY(A, 9) 11 Costruzione di uno heap binario a partire da un array BUILD-HEAP(A) 1 heap-size[A] ← length[A] 2 for i ← length[A] / 2 downto 1 3 do HEAPIFY(A, i) 4 gli elementi del sottoarray A[length[A] / 2 + 1 .. length[A]] sono tutte foglie → ciascuno di essi è uno heap di un solo elemento Marina Zanella – Algoritmi e strutture dati – Ordinamento 12 1 Esempio: BUILD-HEAP(A) Chiamata di HEAPIFY(A, 5) 4 Situazione iniziale 2 3 1 4 i 2 8 5 6 1 16 9 7 4 2 10 9 10 14 8 7 i 3 i 1 8 5 6 1 16 9 9 10 8 5 6 1 16 9 7 10 9 10 2 14 3 2 8 4 4 3 1 4 1 2 1 3 14 8 3 7 10 7 Chiamata di HEAPIFY(A, 4), che dà luogo alla chiamata ricorsiva di HEAPIFY(A, 8) 7 Marina Zanella – Algoritmi e strutture dati – Ordinamento 13 Chiamata di HEAPIFY(A, 2), che dà luogo alle chiamate ricorsive di HEAPIFY(A, 5) e HEAPIFY(A, 10) 1 Esempio: BUILD-HEAP(A) (cont.) 2 i Chiamata di HEAPIFY(A, 3) che dà luogo alla chiamata 8 ricorsiva di HEAPIFY(A, 7) 2 4 3 1 4 10 7 6 1 5 14 1 16 3 9 9 10 8 4 7 4 2 3 16 10 5 14 1 2 3 14 10 5 8 7 8 9 10 4 3 9 9 2 1 Marina Zanella – Algoritmi e strutture dati – Ordinamento 8 1 7 6 1 9 2 7 6 1 10 16 4 7 8 i 3 Chiamata di HEAPIFY(A, 1), che dà luogo alle chiamate ricorsive di HEAPIFY(A, 2), HEAPIFY(A, 4) e HEAPIFY(A, 9) 14 Analisi di BUILD-HEAP HEAPIFY: T(n) = O(h) su un nodo di altezza h → BUILD-HEAP: T(n) = lg n lg n ∞ h h n = O ( n) ∑ h 2 h +1 O (h ) = O (n ∑ 2 h ) = O n∑ h =0 2 h =0 h =0 ∞ n 2 h +1 = n° max di nodi di altezza h Marina Zanella – Algoritmi e strutture dati – Ordinamento h 1/ 2 = =2 ∑ h 2 2 (1 − 1 / 2) h=0 15 Ordinamento di un vettore: HEAPSORT Dato un array A, lo trasforma in uno heap; poi scambia l’elemento radice A[1], che è il max, con l’ultimo elemento dell’array, quindi trasforma in uno heap il sottoarray A[1 .. length[A] – 1], scambia l’elemento radice con l’ultimo elemento del sottoarray, quindi considera il sottoarray decurtato dell’elemento finale, e così via. Alla fine, A contiene gli elementi ordinati in ordine non decrescente. HEAPSORT(A) 1 BUILD-HEAP(A) 2 for i ← length[A] downto 2 3 do scambia A[1] ↔ A[i] 4 heap-size[A] ← heap-size[A] – 1 5 HEAPIFY(A, 1) O(n) O(lgn) T(n) = O(n) + (n – 1)O(lgn) = O(nlgn) (come MERGE-SORT) Marina Zanella – Algoritmi e strutture dati – Ordinamento 16 1 Esempio: HEAPSORT(A) 16 2 3 14 7 4 5 6 2 1 1 2 3 8 7 4 14 2 3 14 1 i=6 1 7 4 5 2 8 5 2 8 1 16 Marina Zanella – Algoritmi e strutture dati – Ordinamento 17 Esempio: HEAPSORT(A) (cont.) i=5 1 1 2 2 3 8 14 2 3 2 8 14 16 1 7 2 7 7 1 i=4 1 3 4 2 2 8 2 7 4 1 1 3 1 16 Marina Zanella – Algoritmi e strutture dati – Ordinamento 18 Esempio: HEAPSORT(A) (cont.) 1 i=3 1 1 2 2 2 2 1 7 8 1 14 16 i=2 1 2 7 8 14 16 Marina Zanella – Algoritmi e strutture dati – Ordinamento 19 Confronto T(n) INSERTION-SORT O(n2) MERGE-SORT O(nlgn) HEAPSORT O(nlgn) Metodo “in loco” non “in loco” “in loco” → HEAPSORT presenta i vantaggi dei due altri algoritmi di ordinamento Marina Zanella – Algoritmi e strutture dati – Ordinamento 20 Coda con priorità È uno degli impieghi più conosciuti dello heap; consente di mantenere un insieme S di elementi, ciascuno dotato di chiave, attraverso le seguenti operazioni: INSERT(S, X): inserisce l’elemento x l’ordinamento delle chiavi nell’insieme S, MAXIMUM(S): restituisce l’elemento di S con chiave più grande rispettando EXTRACT-MAX(S): rimuove e restituisce l’elemento di S con chiave più grande Es. di applicazioni delle code con priorità: • allocazione di processi su un calcolatore (chiave = priorità relativa) • simulazione di un sistema guidato da eventi (chiave = istante di occorrenza di un evento); in questo caso, si usano le operazioni duali MINIMUM e EXTRACT-MIN Marina Zanella – Algoritmi e strutture dati – Ordinamento 21 Coda con priorità (cont.) HEAP-EXTRACT-MAX(A) 1 if heap-size[A] < 1 2 then error “heap underflow” 3 max ← A[1] 4 A[1] ← A[heap-size[A]] 5 heap-size[A] ← heap-size[A] – 1 6 HEAPIFY(A, 1) 7 return max O(1) O(lgn) T(n) = O(lgn) Marina Zanella – Algoritmi e strutture dati – Ordinamento 22 Coda con priorità (cont.) HEAP-INSERT(A, key) 1 heap-size[A] ← heap-size[A] + 1 2 i ← heap-size[A] 3 while i > 1 e A[PARENT(i)] < key 4 do A[i] ← A[PARENT(i)] 5 i ← PARENT(i) 6 A[i] ← key T(n) = O(lgn) (cammino seguito da una nuova foglia fino alla radice) Marina Zanella – Algoritmi e strutture dati – Ordinamento 23 Esempio: HEAP-INSERT(A, 15) 16 14 10 8 2 7 4 3 9 2 2 i 1 i 4 1 3 9 7 10 14 4 10 8 16 8 14 i 1 15 16 9 3 7 Marina Zanella – Algoritmi e strutture dati – Ordinamento 24