Lezione n°5 Algoritmi Avanzati a.a.2014/2015 Prof.ssa Rossella Petreschi Circuiti di ordinamento Utilizziamo il comparatore, ossia un circuito di confronto con due ingressi e due uscite il cui valore è la coppia di valori in input ordinati in modo ascendente. x min(x,y) y max(x,y) I circuiti formati da comparatori, se opportunamente combinati, costituiscono un'architettura in grado di ordinare n valori di input. 9 5 2 2 5 9 7 5 5 2 2 5 7 7 7 7 9 9 Insertion Sort Due architetture per implementare l'insertion sort (tra 8 valori in input): Questa macchina utilizza una versione seriale dell'algoritmo di complessità O(n2) (indicata dalla profondità del circuito, ossia il numero di porte che vengono attraversate in tempi differenti). È possibile ottenere una complessità O(n) ottimizzando lo scheduling dei comparatori e parallelizzando l'algoritmo. Sulla monotonicità Se un circuito di ordinamento trasforma la sequenza di input a = (a1, a2, …, an) nella sequenza di output b = (b1, b2, …, bn), allora per ogni funzione monotona crescente f, il circuito trasforma la sequenza di input f(a) = (f(a1), f(a2), …, f(an)) nella sequenza di output f(b)=(f(b1), f(b2), …, f(bn)). Tale proprietà è facilmente verificata da un singolo comparatore e per induzione la si può provare per un intero circuito di ordinamento. x min(x,y) f(x) min(f(x), f(y))) = f(min(x,y)) y max(x,y) f(y) max(f(x), f(y))) = f(max(x,y)) Principio 0/1 Teorema (principio 0/1): se un circuito combinatorio di ordinamento lavora correttamente per qualunque input costruito sull'alfabeto {0,1} allora lavora correttamente per qualunque input costruito su di un qualsiasi alfabeto finito A. Dim: supponiamo per assurdo che il circuito ordini tutte le sequenze costruite sull'alfabeto {0,1} correttamente, ma che esista una sequenza di input di numeri arbitrari a = (a1, a2, …, an) contenente elementi ai e aj tali che ai < aj mentre il circuito pone aj prima di ai nella sequenza di output. Definiamo una funzione f monotona crescente come: •f(x) = 0 se x <= ai •f(x) = 1 se x > ai dal lemma precedente segue che il circuito sistema f(aj) prima di f(ai) nella sequenza di output quando f(a) è l'input. Ma poiché f(aj) = 1 mentre f(ai) = 0, neghiamo l'ipotesi giungendo ad un assurdo. Sequenze bitoniche Una Sequenza Bitonica è una sequenza che può essere divisa in due sottosequenze monotone, una crescente e l'altra decrescente o viceversa. Sono bitoniche le due sequenze: •m(S) = (min{s1,sn+1}, min{s2,sn+2}, …, min{sn,s2n}) •M(S) = (max{s1,sn+1}, max{s2,sn+2}, …, max{sn,s2n}) ottenute dalla sequenza bitonica S = s1, s2, …, s2n Sfruttando la definizione di m(S) ed M(S) e le relative proprietà si può ottenere una definizione ricorsiva per le sequenze bitoniche che ci permette di realizzare un primo algoritmo di ordinamento che opera ricorsivamente secondo lo schema: S=13896554 S1 = m(S) = 1 3 5 4 S2 = M(S) = 6 5 8 9 S3 = m(S1) = 1 3 S4 = M(S1) = 5 4 S5 = m(S2) = 6 5 S6 = M(S2) = 8 9 13 45 56 89 Complessità O(log(n)). Sequenze pulite Una sequenza binaria si dice pulita se è composta interamente da 0 o da 1. Se S è bitonica almeno una delle due sottosequenze bitoniche m(S) e M(S) è pulita (diretta conseguenza del fatto che la cardinalità di {0,1} è due). In figura è presentato un circuito di ordinamento, di profondità logaritmica, per sequenze 0/1 bitoniche (ad ogni passo rendiamo pulita metà della sequenza). 0 0 1 1 1 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 1 Circuito di fusione Il circuito di fusione fonde due sequenze ordinate costruite sullo stesso alfabeto sfruttando il fatto che, date due sequenze ordinate entrambe crescenti (o decrescenti) x e y, la sequenza che si ottiene concatenando x con z=“y rovesciata” è bitonica (l’inversione della stringa y si realizza semplicemente variando le connessioni). 0 0 0 1 0 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 1 La profondità del circuito è logaritmica e il numero di comparatori ad ogni passo è n/2. Circuito di ordinamento In figura è riportato un circuito di ordinamento che realizza l’ordinamento connettendo iterativamente diversi circuiti di fusione (la base di questa costruzione sta nel fatto che ogni sequenza di due elementi è bitonica). 1 0 0 0 0 1 1 0 0 1 0 0 0 1 0 1 0 0 1 0 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 1 Poiché concateniamo un numero logaritmico di circuiti di fusione, otteniamo una profondità del circuito O(log2 n). Applicando il teorema di Brent Il circuito di ordinamento, O, è un circuito combinatorico di profondità d = O(log2 n), di dimensione pari al numero di comparatori c = O(nlog2n), fan in e fan out limitati. Per il Teorema di Brent, l’algoritmo di ordinamento che lavora su O può essere simulato da una algoritmo che lavora su una PRAM EREW con p processori in tempo O(c / p + d), ovvero O((n log2 n) / p + log2 n). Quando p = O(n) si ha che la complessità temporale dell'ordinamento su una PRAM EREW è O(log2 n) e il costo O(n log2 n). Trasportabilità fra P-RAM con diverso numero di processori Teorema (p' < p): ogni algoritmo A che lavora in tempo parallelo O(t) su una PRAM con p processori può essere simulato da un algoritmo A' che lavora su una PRAM con p' processori (p' < p) in tempo O(t p / p'). Dimostrazione: durante ognuno dei t passi dell’esecuzione di A, i p processori lavorano parallelamente in tempo O(1). Durante ogni passo della esecuzione di A', ciascuno dei p'<p processori eseguirà un blocco seriale di p/p' operazioni in tempo O(p/p'). Pertanto il tempo parallelo relativo alla esecuzione di A' sarà O(tp/p'). Il costo dei due algoritmi A e A' rimane pari a O(tp). Numero di processori limitato (1) Vogliamo sommare n numeri, con la tecnica della prima metà, avendo a disposizione un numero fissato a priori di p processori (p < n). Ricordiamo come si lavora con n processori Begin for i = 1 to log n do for j = 0 to n/2i -1 pardo Pj: A[ j ] = A[ j ] + A[ j+n/2i ] return A[ 0 ] end P1 P 2 P3 P4 4 2 7 4 1 6 3 8 5 8 10 12 15 20 35 Numero di processori limitato (2) p = 2 P1 P2 Vediamo cosa cambia con p <n processori Begin for i = 1 to log n do for s = 0 to (n/2i) / p -1 do for j = 0 to min(n/2i, p) -1 pardo if (j+s*p < n/2i) then Pj: A[ j+s*p ] = A[ j+s*p ] + A[ j+s*p +n/2i ] return A[ 0 ] end i=1 4 2 7 4 1 6 3 8 s=0 5 8 7 4 1 6 3 8 s=1 i=2 5 8 10 12 s=0 i=3 15 20 s=0 35