Esercizi – parte 1 MODELLO RAM E DIMENSIONE DELL’INPUT 1. Si consideri il seguente algoritmo per il calcolo del massimo in un array di n elementi: int arrayMax(int A[], int n) { int i; int currentMax=A[0]; for (i=0; i < n; i++) if (A[i] > currentMax) currentMax=A[i]; return currentMax; } Supponiamo di considerare una piattaforma particolare e di aver stabilito (ad esempio mediante misurazioni sperimentali) che su tale piattaforma i tempi di esecuzione delle operazioni che ci interessano sono i seguenti: • Assegnazione di una variabile intera: 20 ns. • Test: 10 ns. • Incremento di una variabile intera (ad esempio i nel ciclo for): 30 ns. • Attivazione della funzione (allocazione della memoria per le variabili locali, creazione del record di attivazione ecc.): 50 ns. • Esecuzione dell’istruzione return:30ns. Si consideri l’input {7, 1, 4} e si confrontino i costi dell’algoritmo su tale input quando i) si considerano i tempi di calcolo delle operazioni visti sopra e ii) si considerano i tempi di calcolo secondo il modello RAM. Sol. Ponendo convenzionalmente pari ad 1 il costo dell’operazione di test, le operazioni hanno costi 2 (assegnazione), 1 (test), 3 (incremento), 5 (attivazione), 3 (return). Ricalcolando il costo dell’esecuzione usando questi pesi otteniamo il valore 2 (assegnazione iniziale) + 2 (assegnazione nel ciclo for) + 3 (test nel ciclo for) +9(incrementinelfor) + 1 (test finale for) + 3 (test if) + 3 (istruzione return) = 23. Il rapporto tra il costo calcolato in quesdto modo e quello valutato usando il modello definito sopra e’ circa 1,77. E’ opportuno sottolineare che i due costi differiscono per i seguenti motivi (a parte la scelta dell’unit`adi misura, che puo’ essere fatta arbitrariamente): • Il costo di un’operazione elementare non e’ quello stabilito nel modello teorico (a parte le unita’ di misura); ad esempio, il costo dell’incremento e’stato posto pari ad 1 nel modello, ma risulta essere di 3 unita’ in pratica. • I costi relativi delle operazioni sono diversi nei due casi; ad esempio, nel modello teorico l’operazione di test e quella di assegnazione hanno entrambe costo unitario, mentre in pratica l’istruzione di assegnazione costa il doppio dell’operazione di test. Si osservi tuttavia che i due costi sono legati da una costante (2 nel caso particolare considerato). • Nel modello teorico non si considerano i costi di attivazione delle funzioni (o dei metodi, nel caso di linguaggi Object Oriented). A tale riguardo si osservi tuttavia che, essendo l’attivazione eseguita una sola volta per l’esecuzione di una funzione (o di un metodo), tale differenza incide solamente per un fattore costante nella stime del costo di esecuzione. 2. Si supponga che l’input a un problema sia costituito da n numeri interi rappresentati da 2 byte ciascuno. Qual e’ la dimensione dell’input secondo i) il modello RAM e ii) espresso in bit? Sol. i) n; ii) 16n. Si osservi che, in generale, la dimensione dell’input e’ il numero di bit necessari a rappresentarlo. L’esempio che segue mette in evidenza il significato di questa definizione. 3. L’input a un problema e’ rappresentato da un numero intero N. Qual e’ la dimensione dell’input? Sol. Per rappresentare un intero N servono log2N bit (dato un numero reale x, x denota la sua parte intera superiore, ad esempio, 8.9=9). Quindi, per rappresentare 7 servono 3 bit, per rappresentare 13 servono 4 bit e cosi’ via. Cio’ significa che la dimensione dell’input nel caso considerato e’ log2N. Questo aspetto puo’ risultare rilevante in applicazioni numeriche che trattano numeri molto grandi, non rappresentabili esplicitamente con i tipi predefiniti del linguaggio di programmaazione scelto. Ad esempio, l’intero piu’ grande rappresentabile in Java prevede 64 bit (tipo long). Per rappresentare numeri del tipo 2x, con x>64, occorre definire un tipo apposito. ANALISI DEL CASO PEGGIORE 4. Si supponga che un algoritmo per il calcolo del prodotto di due matrici nxn a elementi interi richieda 3n3 operazioni nel caso peggiore. Si supponga per semplicita’ che tutte le operazioni richiedano 10ns per l’esecuzione su una certa piattaforma Hw/Sw. Qual e’ il tempo di esecuzione nel caso peggiore sulla piattaforma considerata quando a) n=100 e b) n=1000? Qual e’ il rapporto tra i tempi di esecuzione nei due casi? Sol. Il tempo di calcolo nel caso peggiore si ottiene banalmente moltiplicando il tempo della singola operazione per il numero di operazioni nel caso peggiore. Il calcolo e’ lasciato allo studente. 5. Si consideri l’algoritmo dell’esercizio 1: int arrayMax(int A[], int n) { int i; int currentMax=A[0]; for (i=0; i < n; i++) if (A[i] > currentMax) currentMax=A[i]; return currentMax; } Se ne valuti la complessita’ nel caso peggiore. Sol. Il caso peggiore si ha quando il numero piu’ grande e’ l’ultimo elemento di A. L’inizializzazione di currentMax ha costo 1. Si paga poi 1 per l’inizializzazione nel ciclo for. Il test e l’incremento nel ciclo for sono eseguiti n-1 volte, quindi il loro costo complessivo e’ 2(n-1). Il test dell’istruzione if e’ eseguito n-1 volte. Nel caso peggiore, anche l’istruzione di riassegnazione di currentMax e’ eseguita n-1 volte. Infine, il costo dell’istruzione return e’ 1. Sommando i contributi si ottiene 4n-1. COMPLESSITA’ ASINTOTICA 6. Si dimostri che la complessita’ asintotica dell’algoritmo al punto 5 e’ O(n). Sol. La complessita’ nel caso peggiore e’ 4n-1, che e’ O(n). Basta scegliere c=4 e n0=1. 7. Mostrare che N lnN = O(N3/2) (es. 2.23 sul testo). Sol. Dobbiamo dimostrare che, per una costante c > 0 (che scegliamo noi secondo le nostre convenienze) e a partire da un valore N0 (che di nuovo scegliamo noi e puo’ dipendere da c) sia ha N lnN cN3/2 per N>N0. Si osservi innanzi tutto che cio’ equivale ad affermare che lnN cN1/2 per N>N0. E’ noto dal corso di Analisi I che la funzione lnN e’ dominata dalla funzione N1/2 per ogni N>0. Nel nostro caso, se N rappresenta la dimensione dell’input a un problema (N vale almeno 1), la condizione vale per c=1 e per ogni N 1. 8. Si supponga di sapere che il tempo di calcolo dell’algoritmo A e’ O(N logN) e quello di B e’ (N3). Cosa implicano queste asserzioni circa le prestazioni relative dei due algoritmi? Sol. Quello che si puo’ affermare e’ che esiste un valore N0 di N a partire dal quale il tempo di calcolo di A e’ definitivamente minore del tempo di calcolo di B. Per comprendere cio’, si denotino con TA(N) e TB(N) i tempi di calcolo di A e B in funzione di N e si osservi che si puo’ affermare quanto segue: i) Esistono una costante a>0 e un valore NA di N tali che TA(N) aN logN per N > NA. ii) Esistono una costante b>0 e un valore NB di N tali che TB(N) > bN3 per N > NB. A questo punto si osservi che, per qualunque coppia di costanti a e b positive, aN logN bN3 a partire da un valore N0 sufficientemente grande. Osservando che i) e ii) valgono contemporaneamente per N>max{NA, NB}, TA(N) TB(N) definitivamente per N > max{NA, NB, N0}. RICORRENZE 9. Ricavare la formula 2.1 sul testo (pag. 49). Dimostrare che CN=O(N2). Sol. CN = N(N+1)/2 (v. testo). Per dimostrare che CN=O(N2) e’ sufficiente osservare che N(N+1)/2 N2 per N 1, quindi CN < cN2 per c = 1 e N > N0 = 1. Si osservi che infinite altre coppie (c, N0) vanno bene. Ad esempio, c=1 e N0 = 2 ecc. 10. Dimostrare che, se CN = CN/2 + 1 allora, CN = O(logN), dove con logN indichiamo il logaritmo in base 2 di N. Si usi il metodo di sostituzione. Si assuma N 2. Sol. Assumiamo che l’affermazione sia vera. Deve allora risultare: Cx clogx per qualche c e per ogni x > N0 (si osservi che c e N0 verranno individuati in un secondo momento). Dunque: CN = CN/2 + 1 clog(N/2) + 1 = clogN – clog2 + 1 = clogN – c + 1 clogN per c 1. Per N0 si puo’ scegliere qualunque valore maggiore di 2. 11. Dimostrare il seguente corollario del risultato precedente: se CN = CN/2 + a, con a una costante positiva, allora, CN = O(logN). Sol. Bisogna procedere come nel caso precedente, cambia il valore della costante c. 12. Dimostrare che, se CN = CN/2 + N allora, CN = O(N). Si usi il metodo di sostituzione. Si assuma N 2. Sol. Assumiamo che l’affermazione sia vera. Deve allora risultare: Cx cx per qualche c e per ogni x > N0 (si osservi che c e N0 verranno individuati in un secondo momento). Dunque: CN = CN/2 + N cN/2 + N = (c/2 + 1)N cN per c 2 e per ogni N 2. Basta dunque scegliere c=2 (o piu’ grande) e N0 = 2 (si ricordi che si e’ supposto N 2). ANALISI DI ALGORITMI 13. Si valuti la complessita’ asintotica del seguente metodo per la ricerca sequenziale di un intero in un array di interi positivi: int seqsearch(int A[], int n, int val) { int i ; for (i=0; i < n; i++) if (A[i] == val) return i; return -1; /* Restituisce –1 se val non trovato in A */ } Sol. Si osservi che il caso peggiore si ha quando val non e’ in A. In tal caso il ciclo for e’ iterato n-1 volte. Il costo nel caso peggiore e’ dunque O(n). 14. Si valuti la complessita’ asintotica del seguente metodo Java per la ricerca binaria di un valore in un array di interi positivi. Si supponga che A contenga almeno 2 elementi. int binsearch(int A[], int n, int val) { /* Restituisce –1 se val non presente/* /* Altrimenti restituisce il primo indice i tale che A[i] == val */ return binsearch(A, 0, n-1, val); /* Invoca metodo con stesso nome e diversa segnatura N/ } int binsearch(int A[], int l, int r, int val) { int m = 0; while (r >=l) { m = (l+r)/2; if (val == A[m}) return m; if (val < A[m]) r = m-1; else l = m+1; } return -1; } Sol. Dobbiamo valutare la complessita’ dell’invocazione binsearch(A, 0, n-1, val) nel caso peggiore. Tale caso si ha ovviamente quando val non e’ tra i valore presenti in A. Ad ogni iterazione lo spazio di ricerca viene dimezzato. Ossia, se nella generica iterazione del ciclo while val non viene trovato nel sottoarray A[l….r], nell’iterazione successiva si considera un sottoarray grande non piu’ della meta’ del precedente (A[l…m] o A[m…r] a seconda del valore di val). In ogni caso, se Cx e’ il costo della ricerca binaria nel caso peggiore in un array di dimensione x, possiamo scrivere: Cx Cx/2 + 6. Il secondo addendo si riferisce al fatto che in ciascuna iterazione del ciclo while vengono al piu’ eseguite 6 operazioni elementari (2 test, 2 assegnazioni e due operazioni aritmetiche nel calcolo di m). Il primo addendo deriva dal fatto che se val non viene trovato nell’array di dimensione x, la sua ricerca prosegue in un array di dimensione al piu’ x/2.Tenendo presente l’esercizio 11, proviamo a dimostrare per sostituzione che Cn = O(logn), dove il logaritmo e’ in base 2. Dobbiamo dunque dimostrare che esistono una costante c e un valore N0 tali che Cn clogn per n > N0. Procediamo per sostituzione e supponiamo che Cx clogx. Possiamo allora scrivere: Cn Cn/2 + 6 clog(n/2) + 6 = clogn – clog6 + 6 clogn quando c 6/log6. Dunque e’ possibile scegliere per c qualsiasi valore uguale o superiore a 6/log6, mentre per N0 possiamo prendere qualunque valore uguale o maggiore di 2 (si ricordi l’assunzione che A contenga almeno 2 elementi). APPLICAZIONI 15. Giovanni ha a disposizione due programmi A e B per l’ordinamento di array interi. Il primo ha costo 2n2 nel caso peggiore, mentre il secondo ha costo 15nlogn nel caso peggiore, dove n e’ il numero di elementi presenti nell’array. Per quali valori di n potrebbe essere conveniente usare l’algoritmo A? Motivare la risposta. Si supponga che 2 sia la base del logaritmo. Sol. Puo’ essere conveniente usare A finche’ 2n2 < 15nlogn. Cio’ si verifica per tutti i valori di n tali che n/logn < 15/2.