Corso: Gestione ed elaborazione grandi moli di dati Lezione del: 8 maggio 2006 Argomento: Algoritmi di ordinamento nel Disk odel: ergesort e ulti-way ergesort con analisi della complessitá di I/O e work. Scribes: Francesco Sambo, Emmanuele Zorzan, Guido Zuccon 1 Introduzione In questa trattazione saranno presentati due dei principali algoritmi utilizzati per ordinare una sequenza S di chiavi: dapprima si analizzerá il tradizionale algoritmo ergesort, ottimo per il modello RA, ma inefficiente nel disk model, proponendo sia lo pseudocodice sia l analisi della complessitá vista come numero di operazioni di I/O e come numero di operazioni in memoria RA (work). Successivamente verrá analizzato l algoritmo ulti-way ergesort, che risulta ottimo nel modello RA. 2 erge Sort Uno degli algoritmi classici, usati per risolvere il problema dell ordinamento di una sequenza S composta da chiavi é il ergesort. L idea alla base di tale algoritmo é il procedimento Divide et Impera, che consiste nella suddivisione del problema in sottoproblemi via via piú piccoli, fino ad ottenere un caso base facilmente risolvibile. Il ergesort opera quindi dividendo la sequenza di chiavi da ordinare in due metá e procedendo all ordinamento delle medesime ricorsivamente. Quando si sono divise tutte le metá si procede alla loro fusione (detta fase di merge) costruendo cosí una sequenza ordinata. el Disk odel si ipotizza sempre che l input e l output del problema siano memorizzati su disco. Definiamo la taglia della memoria RA, in parole, e la taglia della sequenza da ordinare e si assuma che una parola sia sufficiente per contenere una chiave. Si definiscono inoltre S 1 ed S 2 le due metá in cui viene ricorsivamente divisa S, ciascuna di taglia 2. Di seguito é presentato l algoritmo in pseudocodice: 1
E-ergeSort(S) 1 S 2 if ( ) 3 then risolvi il problema interamente in RA 4 else 5 E-ergeSort(S 1 = S[0... /2 1]) 6 E-ergeSort(S 2 = S[/2... 1]) 7 E-erge(S 1, S 2, S) Come si puó notare analizzando il codice appena proposto, l algoritmo E-ergeSort: suddivide ricorsivamente le sequenze fino ad ottenerne di taglia ordina le sottosequenze ottenute in RA fonde le sottosequenze ordinate tramite la funzione E-erge Si procede in questo modo, fino ad ottenere la sequenza finale. Prima di presentare la funzione E-erge é necessario introdurre un po di notazione. Si scrivano le due sottosequenze come S 1 = S1 0S1 1...S 2 1 1 e S 2 = S2 0S1 2...S 2 1 2, dove S1 i, Si 2 denotano gli i-esimi blocchi di ciascuna sottosequenza. Si utilizza una notazione analoga per la sequenza S. E-erge(S 1, S 2, S) 1 carica S1 0 e S0 2 in RA 2 i 1, j 1, l 0; 3 while (l < /) 4 do produci S l e scrivilo su disco 5 l + + 6 if ((in RA ci sono < chiavi di S 1 ) AD (i < 2 )) 7 then {Carica S1 i in RA ; i + + } 8 if ((in RA ci sono < chiavi di S 2 ) AD (j < 2 )) 9 then {Carica S j 2 in RA ; j + + } 2
Figura 1: Le parole piú piccole presenti nei blocchi di S 1 ed S 2 vengono copiate ordinatamente nel blocco di S L algoritmo opera quindi su un massimo di quattro blocchi, per le sottosequenze S 1 ed S 2, dai quali estrae l elemento globalmente piú piccolo e lo copia nel blocco di output (vedi figura??). on appena ha riempito un blocco di S lo scrive su disco e carica, se necessario, i nuovi blocchi di input. Analisi della complessitá di I/O Innanzitutto si analizza la complessitá della funzione E-erge. Essa carica in RA una e una sola volta gli 2 blocchi da ciascuna sottosequenza e scrive una sola volta gli blocchi della sequenza finale; la complessitá di I/O é quindi Θ ( ). Si affronta ora l analisi della complessità di E-ergeSort, che risulta espressa dalla seguente relazione di ricorrenza: { ( Θ ) T (,, ) = 2T ( 2,, ) + Θ ( ) > ella seconda equazione, il primo termine é relativo alle chiamate ricorsive e il secondo termine rappresenta la complessitá di I/O del merge. Per calcolare la complessità, si utilizza l albero della ricorsione di figura??. In ogni nodo dell albero il numero di operazioni di I/O é pari alla taglia associata al nodo divisa per, e quindi, per ogni livello, vengono effettuate Θ ( ) operazioni di I/O. La ricorsione si arresta quando =, e quindi l albero ha profonditá h = log 2 h 2. La complessitá puó dunque essere calcolata come log i=0 = Θ ( (log + 1)) 3
Figura 2: Albero della ricorsione per la funzione ergesort Analisi del work Il work svolto dalla funzione di E-ergeSort é espresso dalla seguente relazione di ricorrenza: { Θ ( log ) W (,, ) = 2W ( 2,, ) + Θ () > Infatti Θ ( log ) operazioni sono necessarie, nel caso base, per ordinare elementi in RA e Θ () confronti vengono eseguiti nella fase di merge. Da qui, applicando la tecnica dell albero della ricorsione si ha che: W(,,) = Θ ( ( log ) + ( log )) = Θ ( log log + log ) = = Θ ( log ). Questo algoritmo è ottimo per quanto riguarda il work, ma non lo è, come vedremo, per quanto concerne il numero di operazioni di I/O. Quindi, volendo riassumere i risultati fino a qui trovati si hanno le seguenti complessitá: { T (,, ) = Θ ( (log + 1)) W (,, ) = Θ ( log ) Analizzando tali complessitá, si puó constatare come non si stia utilizzando in modo efficiente la memoria RA. 4
Si noti come per ogni iterazione del ciclo while si abbiano: 2 elementi di S 1 2 elementi di S 2 elementi di S e quindi si occupa globalmente Θ () spazio di memoria RA; il rimanente spazio non viene utilizzato, dando luogo ad un inefficienza: per questo si passa ora ad introdurre l algoritmo E-WergeSort. 3 ulti-way ergesort Alla base del E-WergeSort si colloca l idea di dividere il problema iniziale, di taglia, in / sottoproblemi di taglia / dove, come giá ricordato in precedenza,, e sono rispettivamente il numero di chiavi della sequenza S da ordinare, la taglia della memoria RA ed il numero di parole che si possono trasferire in una operazione di I/O. Di seguito si propone una realizzazione dell algoritmo E-WergeSort dove S risiede su disco all inizio ed alla fine dell algoritmo. E-WergeSort(S) 1 S 2 if ( ) 3 then risolvi il problema interamente in RA 4 else 5 for i 1 to / 6 do E-WergeSort(S i = S[(i 1) /... i / 1]) 7 E-Werge(S 1, S 2... S /, S) 5
Come prima consideriamo ogni sottosequenza S i suddivisa in blocchi e denotiamo con S j i il blocco j della sottosequenza S i. Usiamo un analoga notazione per S. Il cuore dell algoritmo di E-WergeSort é rappresentato dalla funzione E-Werge: E-Werge(S 1, S 2,..., S /, S) 1 carica S1 0, S0 2,..., S0 / in RA 2 l 0, β blocco vuoto di taglia in RA 3 for i 1 to / 4 do index(i) 1 vettore di appoggio 5 crea una priority queue Q con le prime chiavi di ogni S 0 j (1 i ) 6 while (!Q.isEmpty) 7 do estrai la chiave minima k da Q 8 inserisci k nella prima posizione libera di β 9 if (k S i ) 10 then if ((S index(i) 1 11 then carica S index(i) i 12 index(i) + + i é esaurito) AD (index(i) < )) in RA 13 inserisci in Q la prossima chiave, se ne esiste una, da S i 14 if (β contiene chiavi) 15 then scrivi β come blocco S l su disco 16 l + + 17 considera β come blocco vuoto in RA La funzione E-Werge fonde le singole sottosequenze ordinate S 1,S 2,...,S / nella sequenza S, contenente le chiavi in ordine crescente. Inizialmente vengono caricati in RA / blocchi, uno per ogni sottosequenza, riempiendo cosí completamente la memoria (salvo una piccola porzione di spazio per i conti, che si suppone di avere sempre a disposizione). Viene poi allocato β, uno spazio di memoria pari ad un blocco dove mano a mano andranno inseriti i piú piccoli elementi fra tutte le sequenze. Vengono infine create due strutture dati di appoggio: una priority queue Q, che consente di estrarre efficacemente la chiave minima dall insieme in essa contenuto, ed un array index di taglia /, le cui entry sono gli indici dei prossimi blocchi da caricare, un indice per ogni sottosequenza. Un nuovo blocco viene caricato in RA ogni volta che il blocco precedente di una stessa sottosequenza si esaurisce. Da un lato questo assicura la correttezza dell algoritmo, e dall altro consente di utilizzare al meglio lo spazio di memoria RA. 6
Analisi della complessitá di I/O Si consideri la funzione E-Werge appena presentata. Ogni blocco di una sottosequenza S i viene caricato da hard disk in RA una sola volta; ogni blocco della sequenza S finale verrá scritto una sola volta su hard disk; oltre a quelli appena elencati, non vi sono altri trasferimenti da RA ad hard disk e viceversa. Dalle considerazioni precedenti, si desume che il numero di operazioni di I/O eseguite dall algoritmo é Θ ( ) 1. La complessitá di I/O di E-WergeSort, quindi, é espressa dalla seguente relazione di ricorrenza: { ( Θ ) T (,, ) = T ( /,, ) + Θ ( ) > Consideriamo il caso >. In questo caso la complessitá di I/O associata ad ogni livello dell albero della ricorsione é Θ ( ) e quindi: ( T(,,)= Θ (1 + log rappresenta il numero di livelli dell albero di ricorsione, che é dato dal- dove 1 + log l altezza dell albero, foglie é tale che ) ) / h log appunto, piú uno (vedi figura??). Infatti, il livello h delle =, quindi h = log. Figura 3: Albero della ricorsione per l algoritmo E-WergeSort. 1 Qui e in seguito, per rendere piú snella la notazione, si ipotizza >, altrimenti il termine andrebbe sostituito con il piú preciso. Difatti, per leggere < chiavi bisognerebbe caricare comunque un intero blocco in RA. 7
Sfruttando le proprietá dei logaritmi, si puó scrivere: 1 + log = 1 + log / log / = log /+log / log / = log (/)(/) log / = log / log / Combinando la complessitá di I/O del caso > e quella del caso otteniamo: ( ) T(,,)= Θ log / (1 + log / ) L algoritmo di E-ergeSort a due vie, presentato precedentemente, aveva, invece, una complessitá di I/O pari a Θ ( log ). Confrontando le complessitá di I/O del E-ergeSort a due vie e del E-WergeSort si puó notare come asintoticamente quest ultimo sia piú efficiente. ibliografia [AV88] [Code] A. Aggarwal and J.S. Vitter. The input/output complexity of sorting and related problems. Communications of the AC, 31(9):1116 1127, 1988. Cormen, Leiserson, Rivest and Stain package per LaTeX2e, per la scrittura dello pseudocodice. Sito web http://www.cs.dartmouth.edu/%7ethc/clrscode/. 8