Esercizi di complessità (presi da scritti di esame) Esercizio... (punti 8 in prima approssimazione) L'algoritmo che segue simula un torneo ad "eliminazione diretta"; la procedura elimina, ad ogni passo, simula una fase di eliminazione. Per semplicità supponiamo che i concorrenti siano n, con n che è potenza di 2; inoltre supponiamo che i concorrenti siano numeri interi diversi; tra due vince il maggiore. Algoritmo: var: n, k, j : int concorrenti : array [1.. MAX ] of int leggi ( n ) qui si suppone n MAX per k = 1, 2, 3,... n : leggi (concorrenti[ k ] ) j n div 2 divisione intera while ( j > 0 ) { elimina ( concorrenti, j ) j j div 2 } stampa ( concorrenti [1]) ecco il vincitore! ======================= procedura elimina ( aa : array [1.. MAX ] of int IN-OUT, sup : int IN ) ; { qui si suppone sup MAX var i : int ; per i = 1, 2, 3..., sup: if aa[ 2*i - 1 ] < aa[ 2*i ] then aa[ i ] aa[ 2*i ] else aa[ i ] aa[ 2*i - 1 ] } Domande: a) Calcolare la complessità della chiamata: elimina ( concorrenti, j ) in funzione di j b) Calcolare la complessità dell'algoritmo in funzione di n 1
Risposte: a) Indichiamo con TT ( j ) la complessità della chiamata: elimina ( concorrenti, j ), in funzione di j. I costi sono tutti costanti (passaggio dei parametri, valutazione delle espressioni, esecuzione dell' istruzione if-then-else); il costo totale dipende solo da quante volte si esegue l' if-then-else (cioè quante volte si ripete il ciclo for). Quindi TT ( j ) = a j + b (con a, b costanti oppertune e a> 0) dunque TT ( j ) è in Q( j ). b) Indichiamo con T ( n ) la complessità dell'algoritmo, in funzione di n. I costi sono tutti costanti, tranne che quello delle istruzioni per e while Il costo di per k = 1... n :... è ovviamente lineare in n Il while si ripete per j = n/2, n/4,..., n/(2 i ),..., n/n il costo del while è dunque dato dalla sommatoria (c, d costanti, c > 0 )  (j = n/2,...) (c j + d) = c  (j = n/2,...) j +  (j = n/2,...) d La seconda sommatoria ha per valore circa d( log 2 n ) infatti gli j sono circa log 2 n La prima è c (n/2 + n/4 +... ) = quindi è circa c n c n ( 1/2 + 1/4 + 1/8 +... 1/n) = c n (1-1/n) [ vedere (*) sotto ] Sommando tutto, il termine dominante e` lineare in n quindi T ( n ) è in Q( n ). (*) notare che : 1/2 + 1/4 = 1-1/4 1/2 + 1/4 + 1/8 = 1-1/8 1/2 + 1/4 + 1/8 + 1/16 = 1-1/16 eccetera questo si vede ancora meglio disegnando una "torta" Esercizio... (punti 6 in prima approssimazione) L'algoritmo che segue non fa nulla di interessante... Algoritmo: var: n, val, q : integer pot : array [1.. MAX ] of integer 2
leggi ( n ) qui supponiamo n MAX leggi ( val ) per q = 0, 1, 2,... n-1 : pot [q+1] val + q per q = 2, 3, 4,... n : ppp ( pot, q ) ======================= procedura ppp( aa : array [1.. MAX ] of integer IN-OUT, ind : integer IN ) { qui si suppone ind MAX var aux, cont : integer aux 1 ; cont ind while cont > 0 { aux aux * aa[ind ] cont cont -1 } aa[ind] aux } Domande: a) Calcolare la complessità della chiamata: ppp ( pot, q ) in funzione di q b) Calcolare la complessità dell'algoritmo in funzione di n Risposte: a) Nella procedura ppp tutti i costi (incluso il passaggio dei parametri) sono costanti, tranne quello del while; nel while: test e corpo sono a costo costante; l'esecuzione del corpo si ha per cont = q, q-1,...,1; quindi il while ha un costo del tipo a q + b (a, b constanti, a > 0); dunque il costo dellla chiamata è della forma a q + c ed è in Q( q ). b) Nell'algoritmo, tutto ha costo costante, tranne le due istruzioni per. La prima ( per q = 0, 1, 2,... ) ha test, incrementi e corpo a costo costante, quindi ha un costo totale della forma a n + b (a, b costanti, diverse dalle precedenti, a >0) La seconda ( per q = 2, 3, 4,... n : ppp (pot, q) ) ha un costo proporzionale a: Â ( q = 2, 3,...,n) q = a' n 2 + b'n + c' [vedi nota] Quindi tutto l'algoritmo è in Q( n 2 ). 3
Nota: molti hanno avuto problemi con la somma Nel tentativo di cavarsela hanno scritto ( piu' o meno):  ( q = 2, 3,...,n) q =  ( q = 0, 1,...,n-2) q  ( q = 2, 3,...,n) q Non va bene (basta provare a verificare con n= 3...) Era molto piu' semplice:  ( q = 2, 3,...,n) q = (  ( q = 0, 1,...,n) q ) - 2-1... Esercizio... (punti 6 in prima approssimazione) L'algoritmo che segue non fa nulla di interessante... Algoritmo: var: leggi ( k ) j, k, p: integer aa : array [1.. MAX ] of integer qui supponiamo k MAX per j = 1, 2,... k : leggi (aa[j]) qui supponiamo input corretto p 1 while p < k do { Domanda: } per j = 1, 2,..., p : scrivi a[j] scrivi (" * ") per j = p+1, p+2,..., k : scrivi a[j] p 2 * p Calcolare la complessità dell'algoritmo in funzione di k Precisare se c'è un caso peggiore e,se c'è, qual'è. Risposta: Nell'algoritmo tutti i costi sono costanti, tranne: 4
per j = 1, 2,... k : leggi (aa[j]) costo della forma ak + b (a>1) il while, il cui costo si valuta come segue: -- le istruzioni per k = 1, 2,..., p : scrivi a[k] per k = p+1, p+2,..., n : scrivi a[k] valutate globalmente hanno un costo della forma ck + d (c>1) -- il while si esegue per p = 1, 2, 4, 8,..., 2 s, con 2 s che e` circa k, quindi si esegue log k volte circa; ad ogni iterazione il costo è sempre lo stesso ed è quello indicato sopra piu' costanti (per le altre istruzioni del corpo, il test,...) quindi: ck+d' ( Usando le sommatorie ed ignorando le costanti:  (p=1,2,4,8,...,k) k = k  (p=1,2,4,8,...,k) 1 = k log k ) Quindi il costo totale dell'algoritmo ha la forma che è in Q( k log k). Non c'è un caso peggiore. log k (ck+d') + ak+b + b' Esercizio... (punti 6 in prima approssimazione) L'algoritmo che segue non fa nulla di interessante... var: j, k, p: integer aa : array [1.. MAX ] of integer bb : array [1.. MAX ] of integer leggi ( k ) qui supponiamo 0 < k MAX aa[1] 1 per j = 2, 3,... k : aa[j] aa[j-1]+1 per j = 1,2, 3,... k : bb[j] aa[j] p k while p >0 do { per j = 1, 2,..., p : bb[j] aa[j] * bb[j] p p div 2 } Domanda: Calcolare la complessità dell'algoritmo in funzione di k. Precisare se c'è un caso peggiore e, se c'è, qual'è. 5
Risposta La complessità dell'algoritmo è in Q(k); infatti Non c'è una caso peggiore. Tutto ha costo costante, tranne le due istruzione "per" ed il while. I due " per " hanno, ciascuno, un costo della forma ak+b (a, b costanti, a 1). Quanto al while: il corpo si esegue per p = k, k/2, k/4, k/8,..., k/(2 alla j),... il costodel corpo è della forma cp+d (c, d costanti, c 1); Quindi il costo del while è dato da S (p = k, k/2, k/4, k/8,...) (cp+d) = c S (p =...) p + d S (p =...) 1 Ovviamente il primo termine è quello dominante, quindi possiamo limitarci a calcolare la prima sommatoria; scrivendola per disteso c( k + k/2 + k/4 + k/8 + k/16 +...) = c k (1 + 1/2 + 1/4 + 1/8 + 1/16 +...) che è circa 2ck infatti 1/2 + 1/4 + 1/8 + 1/16 +... è circa 1 (come si vede subito, pensando ad una torta). 6