Didattica e Fondamenti degli
Algoritmi e della Calcolabilità
Quinta giornata
Risolvere efficientemente un problema in P:
ancora sulla sequenza di Fibonacci.
Il problema dell’ordinamento
Guido Proietti
Email: [email protected]
URL: www.di.univaq.it/~proietti/index_personal
1
Punto della situazione
• Stiamo cercando di calcolare efficientemente l’nesimo numero della sequenza di Fibonacci
• Abbiamo progettato 3 algoritmi:
– Fibonacci1, non corretto in quanto approssima la
soluzione
– Fibonacci2, che impiega tempo esponenziale in n
– Fibonacci3/4, che impiega tempo proporzionale ad n
• Dovevate dimostrare che per fibonacci2(n)
T(n) = Fn + 2 (Fn-1) = 3Fn-2
# Foglie
2
# Nodi interni
Copyright © 2004 - The McGraw - Hill Companies, srl
Dimostrazione del Lemma 2
Lemma 2: Il numero di nodi interni di un albero strettamente binario è
pari al numero di foglie – 1.
Dim: Per induzione sul numero di nodi interni, sia detto k:
– Caso base k=1: se c’è un solo nodo interno, poiché per ipotesi deve avere due
figli, tali figli saranno foglie, e quindi il lemma segue.
– Caso k>1: supposto vero fino a k-1, dimostriamolo vero per k nodi interni;
osserviamo che poiché k>1, e l’albero è strettamente binario, abbiamo due
possibilità:
1. Uno dei due sottoalberi della radice è una foglia: in tal caso l’altro
sottoalbero (strettamente binario) contiene k-1 nodi interni, e quindi per
ipotesi induttiva avrà k foglie; allora, il numero totale di foglie è k+1, da
cui segue il lemma;
2. Entrambi i sottoalberi (strettamente binari) contengono nodi interni, in
numero totale di k-1=k1+k2; ma allora, per ipotesi induttiva, conterranno
rispettivamente k1+1 e k2+1 foglie, e quindi il numero totale di foglie è
k1+k2+2=k+1, come volevasi dimostrare.
3
Copyright © 2004 - The McGraw - Hill Companies, srl
Un nuovo algoritmo
• Possiamo sperare di calcolare Fn in tempo inferiore a Θ(n) (in
termini di numero di linee di codice eseguite)? Sembrerebbe
impossibile, perché parrebbe che per arrivare ad Fn dobbiamo
prima calcolare Fi per ogni i<n
• In realtà, a ben pensarci, l’unico costo che siamo sicuri di dover
necessariamente sostenere oltre a quello di lettura dell’input, è
quello di scrittura dell’output; esso è esponenziale nel valore in
input n (cresce infatti come Θ(n)), e richiede quindi la scrittura di
Θ(log n) = Θ(n) bit, ma nel modello RAM le operazioni di lettura
e scrittura hanno tutte costo unitario! In tale modello, potremmo
quindi addirittura pensare di calcolare Fn in tempo costante, ed
effettivamente Fibonacci1 è corretto su una RAM!
• Continuiamo quindi a lavorare su una RAM astratta, pensando
comunque di pagare O(1) per lettura /scrittura di input/output, ma
cerchiamo di ridurre il numero di linee di codice eseguite.
Copyright © 2004 - The McGraw - Hill Companies, srl
Potenze ricorsive
• Fibonacci4 non è il miglior algoritmo
possibile
• È possibile dimostrare per induzione la seguente
proprietà di matrici:
1 1
1 0
n
=
n volte
1 1
1 0
…
1 1
Fn+1 Fn
=
1 0
Fn Fn-1
• Useremo questa proprietà per progettare un
algoritmo più efficiente
Copyright © 2004 - The McGraw - Hill Companies, srl
Prodotto di matrici righe per colonne
 a1,1  a1,n 


A    

a
 n ,1  an ,n 
 b1,1  b1,n 


B    
b


b
n ,n 
 n ,1
n
(AB)i,j= k=1
 ai,k bk,j
i=1,…, n
j=1,…, n
Copyright © 2004 - The McGraw - Hill Companies, srl
Dimostrazione per induzione
Base induzione: n=2
2
1 1
1 0
= 1 1  1 1 = 2 1 = F3 F2
1 0
1 0
1 1
F2 F1
1 1
Hp induttiva:
1 0
n

n-1
=
Fn Fn-1
Fn-1 Fn-2
1 1 = Fn Fn-1  1 1
Fn + Fn-1 Fn
Fn+1 Fn
=
1 0
Fn-1 Fn-2
1 0
Fn-1+ Fn-2 Fn-1 = Fn Fn-1

Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci5
• Osserva che il ciclo arriva fino ad n-1, poiché come abbiamo
n-1
appena dimostrato,
e quindi M[1][1]=Fn
1 1
1 0
=
Fn Fn-1
Fn-1 Fn-2
• Il tempo di esecuzione è T(n)=2+n+n-1 = Θ(n)
• Possiamo migliorare?
Copyright © 2004 - The McGraw - Hill Companies, srl
Calcolo di potenze
• Possiamo calcolare la n-esima potenza elevando al
quadrato la n/2 - esima potenza
• Se n è dispari eseguiamo una ulteriore moltiplicazione
• Esempio: se devo calcolare 38:
38 = (34)2 = [(32)2]2 = [(3·3)2]2 = [(9)2]2 = [(9·9)]2 = [81]2
= 81·81 = 6561
 Ho eseguito solo 3 prodotti invece di 8
• Esempio: se devo calcolare 37:
37 = 3·(33)2 = 3·(3·(3)2)2 = 3·(3·(3·3))2 = 3·(3·9)2 =
3·(27)2 = 3·(27·27) = 3·(729) = 2187
 Ho eseguito solo 4 prodotti invece di 7
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci6
passaggio
per valore
Copyright © 2004 - The McGraw - Hill Companies, srl
Tempo di esecuzione
• Tutto il tempo è speso nella funzione
potenzaDiMatrice
– All’interno della funzione si spende tempo costante
– Si esegue una chiamata ricorsiva con input n/2
• L’equazione di ricorrenza è pertanto:
T(n) = T(n/2) + Θ(1)
Copyright © 2004 - The McGraw - Hill Companies, srl
Metodo dell’iterazione
Si può dimostrare che
T(n) = Θ(log2 n )
Infatti: T(n)=T(n/2)+Θ(1)=(T( n/22)+Θ(1))+Θ(1)=
=((T(n/23)+Θ(1))+Θ(1))+Θ(1)=…
e per k = log2 n si ha n/2k = 1 e quindi
T(n)=((…(T(n/2k)+Θ(1))+…+Θ(1))+Θ(1))+Θ(1)
= T(1)+k∙Θ(1) = Θ(1) + log2 n∙Θ(1) = Θ(log n)
fibonacci6 è quindi esponenzialmente più
veloce di fibonacci5!
Copyright © 2004 - The McGraw - Hill Companies, srl
Riepilogo costi su una RAM
Numero di linee di
codice
Occupazione di
memoria
fibonacci1
Θ(1)
Θ(1)
fibonacci2
Θ(n)
Θ(n)
fibonacci3
Θ(n)
Θ(n)
fibonacci4
Θ(n)
Θ(1)
fibonacci5
Θ(n)
Θ(1)
fibonacci6
Θ(log n)
Θ(log n)*
* per le variabili di lavoro delle Θ(log n) chiamate ricorsive
Copyright © 2004 - The McGraw - Hill Companies, srl
La RAM a costi logaritmici
• La RAM a costi logaritmici è un modello di
calcolo più realistico quando si opera su
algoritmi numerici: per manipolare un
numero n, richiede log n operazioni
• In questo modo, anche l’accesso all’i-esima
locazione di un array non costa più O(1),
ma O(log i)
14
Costi su una RAM a costi logaritmici
fibonacci1
Tempo di esecuzione (si
ricordi che la dimensione
dell’input è Θ(log n))
Occupazione di
memoria (# bit)
Θ(log n) = Θ(n)
Θ(n)
fibonacci2
Θ(n n)
Θ(n)
fibonacci3
Θ(n log n)=Θ(n2)
Θ(n log n)
fibonacci4
Θ(n log n)=Θ(n2)
Θ(n)
fibonacci5
Θ(n log n)=Θ(n2)
Θ(n)
fibonacci6
Θ(log n log n)
= Θ(n log n)
Θ(n log n)*
* per le variabili di lavoro delle Θ(log n) chiamate ricorsive
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmi non numerici
16
Il problema dell’ordinamento
Dato un insieme S di n elementi presi da un
dominio totalmente ordinato, ordinare S
• Esempi: ordinare una lista di nomi
alfabeticamente, o un insieme di numeri, o un
insieme di compiti d’esame in base al cognome
dello studente
• Subroutine in molti problemi
• È possibile effettuare ricerche in array ordinati in
tempo O(log n) (ricerca binaria)
17
Il problema dell’ordinamento
(non decrescente)
• Input: una sequenza di n numeri (reali)
<a1,a2,…,an>
(NOTA: la dimensione dell’input è n)
• Output: una permutazione {1,2,…,n} 
{i1,i2,…,in}, ovvero un riarrangiamento <ai1,
ai2,…, ain> della sequenza di input in modo tale
che ai1  ai2 … ain
18
SelectionSort
Approccio incrementale: assumendo che i primi k elementi
siano ordinati, estende l’ordinamento ai primi k+1
elementi scegliendo il minimo degli n-k elementi non
ancora ordinati e mettendolo in posizione k+1
19
7
2
4
5
3
1
1
2
3
4
5
7
1
2
4
5
3
7
1
2
3
4
5
7
1
2
4
5
3
7
1
2
3
4
5
7
1
2
3
5
4
7
SelectionSort (A)
1.
for k=1 to n-1 do
2.
m=k
3.
for j=k+1 to n do
4.
5.
NOTA: Assumiamo
che il primo elemento
dell’array sia in A[1]
if (A[j] < A[m]) then m=j
scambia A[m] con A[k]
• linea 2: m mantiene l’indice dell’array in cui si trova il minimo
corrente
• linee 3-4: ricerca del minimo fra gli elementi A[k],…,A[n] (m viene
aggiornato con l’indice dell’array in cui si trova il minimo corrente)
• linea 5: il minimo è spostato in posizione k
20
Correttezza
• Si dimostra facendo vedere che alla fine del generico passo
k (k=1,…, n-1) si ha: (i) i primi k elementi sono ordinati e
(ii) contengono i k elementi più piccoli dell’array
• Induzione su k:
– k=1: Alla prima iterazione viene semplicemente selezionato
l’elemento minimo dell’array  (i) e (ii) banalmente verificate.
– k>1. All’inizio del passo k i primi k-1 elementi sono ordinati e
sono i k-1 elementi più piccoli nell’array (ipotesi induttiva).
Allora la tesi segue dal fatto che l’algoritmo seleziona il minimo
dai restanti n-k elementi e lo mette in posizione k. Infatti:
(ii) i primi k elementi restano i minimi nell’array
(i) l’elemento in posizione k non è mai più piccolo dei primi k-1
elementi
21
Caso migliore di un algoritmo
• Sia tempo(I) il tempo di esecuzione di un
algoritmo sull’istanza I
Tbest(n) = min istanze I di dimensione n {tempo(I)}
• Intuitivamente, Tbest(n) è il tempo di
esecuzione sulle istanze di ingresso che
comportano meno lavoro per l’algoritmo,
mentre T(n) ricordiamo che denotava il
tempo di esecuzione sulle istanze di
ingresso che comportano più lavoro per
l’algoritmo (c’era un max invece di min)
22
Caso medio di un algoritmo
• Sia P(I) la probabilità di occorrenza dell’istanza I
Tavg(n) = ∑ istanze I di dimensione n {P(I) tempo(I) }
• Intuitivamente, Tavg(n) è il tempo di
esecuzione nel caso medio, ovvero sulle
istanze di ingresso “tipiche” per il problema
• Richiede di conoscere una distribuzione di
probabilità sulle istanze
23
Complessità
temporale
SelectionSort (A)
1.
for k=1 to n-1 do
2.
m=k
3.
for j=k+1 to n do
4.
5.
1 assegnamento
n-k confronti
(operaz. dominante)
1 scambio
(3 assegnamenti)
if (A[j] < A[m]) then m=j
scambia A[m] con A[k]
il tutto eseguito
per k=1,…, n-1
Denotiamo con T(n) il costo di esecuzione dell’algoritmo su una generica
esecuzione; si noti che T(n) ≤ Tworst(n) e T(n)≥Tbest(n)
n-1
n-1
k=1
k=1
T(n) =  [1+(n-k)+1]=2(n-1)+ k =2(n-1)+n·(n-1)/2 = (n2)
Si noti che T(n) è SEMPRE UGUALE ad un polinomio di 2º grado in n,
e quindi la notazione Θ è perfettamente ESPRESSIVA del valore di T(n)
24
 T(n) = Tbest(n) = Tavg(n) = (n2)