doc

annuncio pubblicitario
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.
Scarica