Il problema dell’ordinamento Una rapida panoramica (con qualche dettaglio) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Il problema dell’ordinamento Dato un insieme S di n oggetti presi da un dominio totalmente ordinato, ordinare S Esempi: ordinare una lista di nomi alfabeticamente, o un insieme di numeri, o un insieme di compiti d’esame in base al cognome dello studente Si noti che: • E’ un sottoproblema in molti problemi • E’ possibile effettuare ricerche in array ordinati in tempo O(log n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Algoritmi e tempi tipici • Numerosissimi algoritmi • Tre tempi tipici: O(n2), O(n log n), O(n) Qualche esempio (per confrontare gli ordini di grandezza): n 10 100 n log2 n ~ 33 ~ 665 n2 100 104 1000 106 109 ~ 104 ~ 2·107 ~ 3·1010 106 1012 1018 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Il modello basato su confronti • In questo modello, per ordinare è possibile usare solo confronti tra oggetti • Primitive quali operazioni aritmetiche (somme o prodotti), logiche (and e or), o altro (shift) sono proibite • Sufficientemente generale per catturare le proprietà degli algoritmi più noti (e usati) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Lower bound per il problema dell’ordinamento (nel modello basato su confronti) • Lower bound: delimitazione inferiore alla quantità di una certa risorsa di calcolo necessaria per risolvere il problema • Ω(n log n) è un lower bound al numero di confronti richiesti per ordinare n oggetti (nel modello basato su confronti) • Consideriamo un generico algoritmo A, che ordina eseguendo solo confronti: dimostreremo che A esegue Ω(n log n) confronti F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Albero di decisione per il problema dell’ ordinamento (nel modello basato su confronti) Descrive le diverse sequenze di confronti che A potrebbe fare su istanze di lunghezza n 1:2 ≤Š 2:3 Š ≤ 1,2,3 ≤Š 1,3,2 1:3 > 1:3 > ≤Š > 3,1,2 2,1,3 ≤ Š 2,3,1 > 2:3 > 3,2,1 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Proprieta’: • Per una particolare istanza, i confronti eseguiti da A su quella istanza rappresentano un cammino radice - foglia • Il numero di confronti nel caso peggiore è pari all’altezza dell’albero di decisione • Un albero di decisione per l’ordinamento di n elementi contiene almeno n! foglie F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Altezza in funzione delle foglie: • Un albero binario con k foglie tale che ogni nodo interno ha esattamente due figli ha altezza almeno log2 k • Dimostrazione per induzione su k F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Il lower bound Ω(n log n) • L’altezza dell’albero di decisione è almeno log2 (n!) • Formula di Stirling: n! ~ (2πn)1/2 ·(n/e)n Quindi: log (n!) ~ n log n - O(n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Ordinamenti quadratici F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) SelectionSort Approccio incrementale: estende l’ordinamento da k a k+1 elementi, scegliendo il minimo degli n-k elementi non ancora ordinati e mettendolo in posizione k+1. Esempio di esecuzione: 7 2 4 5 3 1 1 2 3 4 5 7 1 2 4 5 3 7 1 2 3 4 5 7 1 2 4 5 3 7 1 2 3 4 5 7 1 2 3 5 4 7 Tempo di esecuzione nei casi •pessimo: O(n2) •ottimo: O(n2) •medio: O(n2) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) InsertionSort Approccio incrementale: estende l’ordinamento da k a k+1 elementi, posizionando l’elemento (k+1)-esimo nella posizione corretta rispetto ai primi k elementi. Esempio di esecuzione: 7 2 4 5 3 1 2 4 5 7 3 1 2 7 4 5 3 1 2 3 4 5 7 1 2 4 7 5 3 1 1 2 3 4 5 7 Tempo di esecuzione nei casi •pessimo: O(n2) •ottimo: O(n) •medio: O(n2) (Attenzione: nella codifica in [Dem] (pag. 87) il caso ottimo non vale!) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Una codifica di InsertionSort (migliore di quella in [Dem] (capitolo 4, pag. 87)) Insertion-sort (A) { I.C.: A[1..j-1] ordinato, sia U= A[1..j −1] } for j ← 2 to length(A) do key ← A[j] {inserisci A[j] in A[1. .j-1] spostando a destra gli elementi > di A[j]} i ← j-1 { I.C.: A[1..i] A[i+2..j] = U & A[i+2..j] > key } while i > 0 and A[i] > key do A[i+1] ← A[i] i ← i-1 {A[1..i] A[i+2..j] = U & A[i+2..j] > key & A[i] ≤ key } A[i+1] ← key F. Damiani - Alg. & Lab. 04/05 { A[1..length(A)] è ordinato} BubbleSort • Esegue una serie di scansioni dell’array In ogni scansione confronta coppie di elementi adiacenti, scambiandoli se non sono nell’ordine corretto Dopo una scansione in cui non viene effettuato nessuno scambio l’array è ordinato • Dopo la k-esima scansione, i k elementi più grandi sono correttamente ordinati ed occupano le k posizioni più a destra correttezza e tempo di esecuzione O(n2) nel caso pessimo e O(n) nel caso ottimo. Il caso medio e’ O(n2). F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Esempio di esecuzione: (1) 7 2 4 5 3 1 2 7 4 7 5 7 3 7 1 7 (2) 2 4 5 3 1 7 2 4 4 5 3 5 1 5 (3) 2 4 3 1 5 7 2 4 3 4 1 4 (4) 2 3 1 4 5 7 2 3 1 3 (5) 2 1 3 4 5 7 1 2 1 2 3 4 5 7 2 4 3 1 5 7 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Ordinamenti ottimi (nel modello basato su confronti) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) MergeSort Usa la tecnica del divide et impera: 1. Divide: dividi l’array a metà 2. Risolvi il sottoproblema ricorsivamente 3. Impera: fondi le due sottosequenze ordinate F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Esempio di esecuzione: 7 2 4 5 3 1 5 6 input 1 2 3 4 5 5 6 7 output 7 2 4 5 3 1 5 6 2 4 5 7 1 3 5 6 7 2 4 5 3 1 5 6 2 7 4 5 1 3 5 6 Input ed output delle chiamate ricorsive 7 2 4 5 3 1 5 6 7 2 4 5 3 1 5 6 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Codifica di MergeSort (ci sono piccole differenze rispetto a quella in [Dem]) MergeSort (A, p, q) {1 ≤ p ≤ q ≤ length(A)} if p < q then r ← (p + q)/2 {1 ≤ p ≤ r ≤ length(A)} MergeSort (A, p, r) {A[p..r] ordinato} {1 ≤ r+1 ≤ q ≤ length(A)} MergeSort (A, r+1, q) {A[p..r] ordinato & A[r+1..q] ordinato} Merge (A, p, r, q) {A[p..q] ordinato} { A[p..q] ordinato } F. Damiani - Alg. & Lab. 04/05 La procedura Merge • Due array ordinati A e B possono essere fusi rapidamente: estrai ripetutamente il minimo di A e B e copialo nell’array di output, finché A oppure B non diventa vuoto copia gli elementi dell’array non vuoto alla fine dell’array di output • Tempo: O(n), dove n è il numero totale di elementi F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Codifica di Merge (1/2) (ci sono piccole differenze rispetto a quella in [Dem]) Merge (A, p, r, q) { A[p..r] e` ordinato & A[r +1..q] e` ordinato } i ← p j ← r+1 k←p B array di dimensione q-p+1 { I.C.: B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato & A[i..r] ≥ B[1..k-1] & A[j..q] ≥ {B[1..k-1] & BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) } while i ≤ r and j ≤ q do if A[i] ≤ A[j] then B[k] ← A[i] i ← i+1 else B[k] ← A[j] j ← j+1 k ← k+1 { B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato & A[i..r] ≥ B[1..k-1] & A[j..q] ≥ B[1..k-1] & (i > r ∨ j > q) F. Damiani - Alg. & Lab. 04/05 & BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) } Codifica di Merge (2/2) { B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato & A[i..r] ≥ B[1..k-1] & A[j..q] ≥ B[1..k-1] & (i > r ∨ j > q) & BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) } if j > q then B[k..q-p+1] ← A[i..r] else {i > r} B[k..q-p+1] ← A[j..q] {B[1..q-p+1] e` ordinato} A[p..q] ← B[1..q-p+1] {A[p..q] e` ordinato} F. Damiani - Alg. & Lab. 04/05 Tempo di esecuzione • Il numero di operazioni primitive del MergeSort è descritto dalla seguente relazione di ricorrenza: T(n) = 2*T(n/2) + O(n) • Usando il Teorema Master si ottiene T(n) = O(n log n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Spazio occupato durante l’esecuzione • Ogni record di attivazione di MergeSort richiede spazio O(1) quando la procedura merge non e’ attiva Ci possono essere alpiu’ O(log n) chiamate ricorsive attive contemporaneamente Ci puo’ essere una sola Merge attiva • La procedura Merge alloca un vettore di dimensione n (una codifica migliore richiederebbe un vettore di dimensione n/2) • Quindi lo spazio richiesto e’ S(n) = O(n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) HeapSort • • • Stesso approccio incrementale del selectionSort Usa però una struttura dati per estrarre in tempo O(log n) il massimo ad ogni iterazione Struttura dati heap associata ad un insieme S = albero binario radicato con le seguenti proprietà: 1. completo fino al penultimo livello 2. gli elementi di S sono memorizzati nei nodi dell’albero (chiave(v) denota l’elemento memorizzato nel nodo v) 3. chiave(padre(v)) ≥ chiave(v) per ogni nodo v diverso dalla radice F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) La struttura dati heap Rappresentazione ad albero e con vettore posizionale sin(i) = 2i des(i) = 2i+1 parent(i) = i/2 37 22 31 13 7 15 12 3 25 14 9 vettore posizionale 0 37 22 31 13 15 25 14 7 3 12 1 8 9 2 3 4 5 6 7 9 10 11 Se le foglie nell’ultimo livello sono compattate a sinistra (heap con struttura rafforzata), il vettore posizionale ha esattamente dimensione n F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Proprietà salienti degli heap 1) Il massimo è contenuto nella radice 2) L’albero ha altezza O(log n) 3) Gli heap con struttura rafforzata possono essere rappresentati in un array di dimensione pari a n F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) La procedura fixHeap Se tutti i nodi di H tranne v soddisfano la proprietà di ordinamento a heap (ovvero v non e’ maggiore di entrambi i suoi figli), possiamo ripristinarla come segue: fixHeap(nodo v, heap H) if (v è una foglia) then return else sia u il figlio di v con chiave massima if ( chiave(v) < chiave(u) ) then scambia chiave(v) e chiave(u) fixHeap(u,H) Tempo di esecuzione: O(log n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Estrazione del massimo • Copia nella radice la chiave contenuta nella la foglia più a destra dell’ultimo livello • Rimuovi la foglia • Ripristina la proprietà di ordinamento a heap richiamando fixHeap sulla radice Tempo di esecuzione: O(log n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Esempio di estrazione del massimo (1) 16 15 14 7 3 9 2 1 8 10 4 F. Damiani - Alg. & Lab. 04/05 Esempio di estrazione del massimo (2) 4 15 14 7 3 9 2 8 10 1 F. Damiani - Alg. & Lab. 04/05 Esempio di estrazione del massimo (3) 15 4 14 7 3 9 2 8 10 1 F. Damiani - Alg. & Lab. 04/05 Esempio di estrazione del massimo (4) 15 9 14 7 3 4 2 8 10 1 F. Damiani - Alg. & Lab. 04/05 Esempio di estrazione del massimo (5) 15 9 14 7 3 4 2 8 10 1 F. Damiani - Alg. & Lab. 04/05 Costruzione dell’heap (algoritmo ricorsivo) Algoritmo ricorsivo basato sul divide et impera heapify(heap H) {nota: H non e’ ancora uno heap} if (H è vuoto) then return else heapify(sottoalbero sinistro di H) heapify(sottoalbero destro di H) Tempo di esecuzione: T(n)= 2T(n/2)+O(log n) fixHeap(radice di H,H) T(n) = O(n) dal Teorema Master F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Costruzione dell’heap (algoritmo iterativo) Algoritmo iterativo (per la rappresentazione con vettore) heapifyI(heap H) {nota: H non e’ ancora uno heap} for i=indexOfLastIn(H) to 1 do fixHeap(nodo i, heap con radice in i) Tempo di esecuzione: le chiamate di fixheap sono le stesse effettuate dalla versione ricorsiva T(n) = O(n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Esempio di costruzione dell’Heap (1) 2 6 18 3 42 44 94 12 55 79 2 6 18 3 42 12 55 44 94 79 1 2 3 4 5 6 7 8 9 10 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (2) 2 6 18 3 42 44 94 12 55 79 2 6 18 3 42 12 55 44 94 79 1 2 3 4 5 6 7 8 9 10 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (3) 2 6 18 3 79 44 94 12 55 42 2 6 18 3 79 12 55 44 94 42 1 2 3 4 5 6 7 8 9 10 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (4) 2 6 18 94 79 44 3 12 55 42 2 6 18 94 79 12 55 44 3 42 1 2 3 9 10 4 5 6 7 8 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (5) 2 6 55 94 79 44 3 12 18 42 2 6 55 94 79 12 18 44 3 42 1 2 3 9 10 4 5 6 7 8 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (6) 2 94 55 44 79 6 3 12 18 42 2 94 55 44 79 12 18 6 3 42 1 2 8 9 10 3 4 5 6 7 F. Damiani - Alg. & Lab. 04/05 Esempio di costruzione dell’Heap (7) 94 79 55 44 42 6 3 12 2 94 79 55 44 42 12 18 1 18 2 3 4 5 6 7 6 3 2 8 9 10 F. Damiani - Alg. & Lab. 04/05 L’algoritmo HeapSort • Costruisce un heap tramite heapify • Estrae ripetutamente il massimo per n-1 volte O(n) O(n log n) ad ogni estrazione memorizza il massimo nella posizione dell’array che si è appena liberata ordina in loco in tempo O(n log n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Esempio di esecuzione: 37 22 31 13 15 25 31 25 13 15 22 25 22 13 15 37 31 22 13 15 25 31 25 22 13 15 37 25 15 22 13 31 37 (1) (2) (3) 22 15 15 13 13 13 22 15 13 25 31 37 15 13 22 25 31 37 13 15 22 25 31 37 (4) (5) (6) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) ATTENZIONE Alcuni autori usano nomi diversi per le operazioni sull’heap. Ad esempio, in alcuni testi: • fixHeap e’ chiamata heapify, e • heapify e’ chiamata buildHeap F. Damiani - Alg. & Lab. 04/05 QuickSort Usa la tecnica del divide et impera: 1. Divide: scegli un elemento x della sequenza (perno) e partiziona la sequenza in elementi ≤ x ed elementi >x 2. Risolvi i due sottoproblemi ricorsivamente 3. Impera: restituisci la concatenazione delle due sottosequenze ordinate Rispetto al MergeSort, divide complesso ed impera semplice F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Codifica di QuickSort (ci sono piccole differenze rispetto a quella in [Dem]) Quicksort (A, p, q) {p ≤ q+1} if p < q then {p < q} r ← Partition (A, p, q) {A[p..r-1] ≤ A[r] ≤ A [r+1..q]} {p ≤ r (ovvero: p ≤ (r-1)+1 } Quicksort (A, p, r -1) {A[p..r-1] ordinato & A[p..r-1] ≤ A[r] ≤ A [r+1..q]} {r ≤ q (ovvero: r+1 ≤ q+1) } Quicksort (A, r+1, q) {A[p..r-1] ordinato & A[r+1..q] ordinato & & A[p..r-1] ≤ A[r] ≤ A [r+1..q] } {A[p..q] e` ordinato} F. Damiani - Alg. & Lab. 04/05 Partizione in loco • Scorri l’array “in parallelo” da sinistra verso destra e da destra verso sinistra da sinistra verso destra, ci si ferma su un elemento maggiore del perno da destra verso sinistra, ci si ferma su un elemento minore del perno • Scambia gli elementi e riprendi la scansione Tempo di esecuzione: O(n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Partizione in loco, un esempio (attenzione: esecuzione del codice presentato in [Deme], in cui il pivot viene mantenuto in posizione 1, e alla fine si effettua poi uno scambio): 45 12 93 3 67 43 85 29 24 92 63 3 21 45 12 21 3 67 43 85 29 24 92 63 3 93 45 12 21 3 3 43 85 29 24 92 63 67 93 45 12 21 3 3 43 24 29 85 92 63 67 93 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Codifica di Partition (ci sono piccole differenze rispetto a quella in [Dem]) Partition (A, p, q) {p < q} x ← A[p] i ← p-1 j ← q+1 {A[p..i] ≤ x & A[j..q] ≥ x} while true do do j ← j-1 while A[j] > x do i ← i+1 while A[i] < x {A[p..i-1] ≤ x & A[j+1..q] ≥ x & A[i] ≥ x & A[j] ≤ x} if i < j then {A[p..i-1] ≤ x & A[j+1..q] ≥ x & A[i] ≥ x & A[j] ≤ x & i < j} “scambia A[i] con A[j]” else {A[p..j-1] ≤ A[j] ≤ A[j+1..q]} return j F. Damiani - Alg. & Lab. 04/05 Qucksort, esempio di esecuzione: 5 5 6 7 1 2 3 4 2 5 4 3 1 5 7 6 1 2 3 4 5 5 6 7 2 5 4 3 1 1 2 4 3 5 1 2 3 4 5 1 1 1 dopo partition output 7 6 6 7 6 7 4 3 5 3 4 5 3 4 5 3 3 3 input 6 6 6 L’albero delle chiamate ricorsive può essere sbilanciato 5 5 5 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Analisi di complessita’ in tempo Siano T(n) e P(n) il numero di confronti tra elementi di A eseguiti rispettivamente da Quicksort e Partition. Poiche’ P(n) = n e le due porzioni di A sulle quali avviene la chiamata ricorsiva hanno j - 1 e n - j elementi otteniamo: T(n) = 0 T(n) = n + T(j - 1) + T(n - j) per n < 2 per n ≥ 2 F. Damiani - Alg. & Lab. 04/05 Caso peggiore Nel caso peggiore, il perno scelto ad ogni passo è il minimo (o il massimo) degli elementi nell’array. Se A[p] e’ il minimo: la porzione di A con elementi minori di A[p] e’ vuota, mentre la porzione di A con elementi maggiori o uguali contiene n-1 elementi. L’equazione di ricorrenza assume la forma: T(n) = 0 T(n) = T(n - 1) + n per n < 2 per n ≥ 2 F. Damiani - Alg. & Lab. 04/05 n n-1 Albero della ricorsione di Quicksort nel caso peggiore n-2 T(n) = Θ(n2) 2 F. Damiani - Alg. & Lab. 04/05 Caso migliore A e’ sempre suddiviso a meta’: la porzione di A con elementi minori di A[p] e quella con elementi maggiori o uguali contiene (n-1)/2 elementi. L’equazione di ricorrenza assume la forma: T(n) = 0 T(n) = 2 T((n - 1)/2 ) + n per n < 2 per n ≥ 2 T(n) = Θ(n lgn) F. Damiani - Alg. & Lab. 04/05 Randomizzazione • Possiamo evitare il caso peggiore scegliendo come perno un elemento a caso • Poiché ogni elemento ha la stessa probabilità, pari a 1/n, di essere scelto come perno, il numero di confronti nel caso atteso è: n n T(n) = n + 1/n Σ (T(j - 1) + T(n - j)) = n + 2/n Σ T(j) j=1 j=1 dove j-1 e n-j sono le dimensioni dei problemi risolti ricorsivamente. (In [Dem] si considera invece la relazione di ricorrenza: n-1 T(n)= ∑ a=0 n-1 2 T(a) 1 (n-1+T(a)+T(n-a-1)) = n-1+ ∑ — — n n a=0 dove a e (n-a-1) sono le dimensioni dei sottoproblemi risolti ricorsivamente.) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Numero di confronti nell’algoritmo randomizzato (analogo a quello nel caso medio, nell’ipotesi che i possibili input, ugualmente probabili, siano tutte le permutazioni di n elementi distinti) Partition produce un misto di suddivisioni “buone” e “cattive”, che sono distribuite lungo l’albero in modo casuale. n T(n) = n + 1/n Σ (T(j - 1) + T(n - j)) j=1 n-1 = n + 2/n Σ T(j) j=1 La soluzione dell’equazione richiede qualche manipolazione algebrica: F. Damiani - Alg. & Lab. 04/05 T(n) = n + 2/n n-1 Σ T(j) j=1 n-2 T(n-1) = n -1 + 2/(n-1) Σ T(j) j=1 (n-1) (n-1) (n-1) T(n-1) = n n (n-1) T(n-1) T(n) n n-2 + 2/n Σ T(j) j=1 2n-1 = n + 2/n T(n-1) 2n-1 n+1 T(n) = n + n T(n-1) n+1 ≤ 2 + n T(n-1) F. Damiani - Alg. & Lab. 04/05 n+1 T(n) ≤ 2 + n T(n-1) n+1 n ≤ 2 + n (2 + n-1 ≤ 2+2 n+1 n + T(n-2)) n+1 T(n-2) n-1 ≤ 2 + 2 n+1 + 2 n+1 + 2 n+1 . . . + (n+1) T(1) n n-1 n-2 2 n+1 1 1 +. . . + 1 ) = 2 + 2(n+1) ( + n 3 n-1 n+1 n+1 = 2 (n+1) Σ 1/k k=3 T(n) = O(n log n) F. Damiani - Alg. & Lab. 04/05 Analisi di complessita’ in spazio In base alle considerazioni fatte in precedenza sulle possibili forme dell’albero delle chiamate ricorsive di QuickSort abbiamo che (dato che Partition opera in loco) lo spazio richiesto e’: S(n) = O(log n) nel caso ottimo S(n) = O(n) nel caso peggiore S(n) = O(log n) nel caso medio (ovvero, nel caso atteso dell’algoritmo randomizzato) . F. Damiani - Alg. & Lab. 04/05 Osservazioni • Possiamo ridurre ulteriormente la probabilita’ che si verifichi il caso peggiore scegliendo come perno il mediano di 3 (o 5 o7) elementi scelti a caso. • Possiamo migliorare ulteriormente le prestazioni gestendo i sottoproblemi di dimensione piccola (ad es. ≤ 10) con un insertion sort. • Il quicksort non e’ stabile (la nozione di stabilita’ e’ introdotta piu’ avanti, in questi lucidi): controllate il funzionamento sia della procedura partition presentata in questi lucidi che di quella presentata in [Deme]. F. Damiani - Alg. & Lab. 04/05 Esercizio • Se si modifica la procedura partition presentata in questi lucidi sostituendo le righe: do j ← j-1 while A[j] > x do i ← i+1 while A[i] < x con le righe: do j ← j-1 while A[j] ≥ x do i ← i+1 while A[i] ≤ x si ottiene una procedura partition che e’ ancora corretta, ma che tuttavia presenta un inconveniente. Quale? (Suggerimento: considerate la situazione si verifica in cui tutti gli elementi da ordinare hanno la stessa chiave.) F. Damiani - Alg. & Lab. 04/05 Ordinamenti lineari (per dati di input con proprietà particolari) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) IntegerSort: fase 1 Per ordinare n interi con valori in [1,k] Mantiene un array Y di k contatori tale che Y[x] = numero di volte che il valore x compare nell’array di input X Esempio di esecuzione: X 5 1 6 8 6 Y 0 1 0 2 0 3 0 4 1 5 X 5 1 6 8 6 Y 1 1 0 2 0 3 0 4 1 5 0 6 1 6 0 7 0 7 0 8 1 8 5 1 6 8 6 1 1 0 2 0 3 0 4 1 5 5 1 6 8 6 1 1 0 2 0 3 0 4 1 5 0 6 0 7 0 8 2 6 0 7 1 8 5 1 6 8 6 1 1 0 2 0 3 0 4 1 5 1 6 0 7 (a) Calcolo di Y F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) 0 8 IntegerSort: fase 2 Scorre Y da sinistra verso destra e, se Y[x]=k, scrive in X il valore x per k volte X 1 1 Y 1 1 0 2 0 3 0 4 X 1 5 6 6 Y 0 1 0 3 0 4 0 2 1 5 0 5 2 6 2 6 0 7 0 7 1 8 1 8 0 1 0 2 0 3 0 4 1 5 6 6 0 1 0 3 0 4 0 2 1 5 0 5 2 6 0 6 0 7 0 7 1 8 1 8 1 5 0 1 0 2 0 3 0 4 1 5 1 5 6 6 8 0 1 0 2 0 3 0 4 0 5 2 6 0 7 1 8 0 6 0 7 1 8 (b) Ricostruzione di X F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) IntegerSort: analisi • Tempo O(k) per inizializzare Y a 0 • Tempo O(n) per calcolare i valori dei contatori • Tempo O(n+k) per ricostruire X O(n+k) Tempo lineare se k=O(n) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) BucketSort Per ordinare n record con chiavi intere in [1,k] • Basta mantenere un array di liste, anziché di contatori, ed operare come per IntegerSort • La lista Y[x] conterrà gli elementi con chiave uguale a x • Concatenare poi le liste Tempo O(n+k) come per IntegerSort F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) La stabilità • Un algoritmo è stabile se preserva l’ordine iniziale tra elementi con la stessa chiave • Il BucketSort può essere reso stabile appendendo gli elementi di X in coda alla opportuna lista Y[i] F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) RadixSort • Cosa fare se il massimo valore k e’ > di n, ad esempio se k = Θ(nc)? • Rappresentiamo gli elementi in base b, ed eseguiamo una serie di BucketSort • Partiamo dalla cifra meno significativa verso quella più significativa Per b=10 2397 4368 5924 5924 2397 4368 5924 4368 2397 4368 2397 5924 2397 4368 5924 F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Correttezza • Se x e y hanno una diversa t-esima cifra, la t-esima passata di BucketSort li ordina • Se x e y hanno la stessa t-esima cifra, la proprietà di stabilità del BucketSort li mantiene ordinati correttamente Dopo la t-esima passata di BucketSort, i numeri sono correttamente ordinati rispetto alle t cifre meno significative F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Tempo di esecuzione • O(logb k) passate di bucketsort • Ciascuna passata richiede tempo O(n+b) O( (n+b) logb k) ( ) logk Se b = Θ(n), si ha O(n logn k)=O n —— logn Tempo lineare se k=O(nc), c costante F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Riepilogo • Nuove tecniche: Divide et impera (MergeSort, QuickSort) Randomizzazione (QuickSort) Strutture dati efficienti (HeapSort) • Proprietà particolari dei dati in ingresso possono aiutare a progettare algoritmi più efficienti: algoritmi lineari • Alberi di decisione per la dimostrazione di delimitazioni inferiori F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill) Domande… IntegerSort, BucketSort, RadixSort non ordinano in loco… Se qualcuno vi dicesse che ha trovato un algoritmo di ordinamento che permettere di ordinare l’insieme {1,…,n} in loco con complessita’ in tempo O(n) nel caso pessimo, che cosa gli rispondereste? F. Damiani - Alg. & Lab. 04/05 Esercizio • Dimostrare, con il metodo delle asserzioni, la correttezza delle codifiche degli algoritmi selectionSort, insertionSort, bubleSort, mergeSort (e merge), quickSort (e partition), integerSort, bucketSort, radixSort contenute in [Dem]. ANNOTATE (SCRIVENDO A MATITA SUL LIBRO) IL CODICE CON LE ASSERZIONI CHE NE DIMOSTRANO LA CORRETTEZZA. F. Damiani - Alg. & Lab. 04/05