Sei sulla pagina 1di 49

Algoritmi e Strutture Dati

Lezione 2
Ricorsione
Analisi degli algoritmi
Oggi parleremo di …

Ricorsione
Analisi degli algoritmi
 complessità
 temporale
 spaziale
 notazioni O, , 

2
Ricorsione (non si sa mai!)

Un algoritmo (o una funzione) si dice ricorsivo (o


ricorsiva) quando contiene chiamate a se stesso/a.

Ricorsione diretta
 A chiama A
Ricorsione indiretta
 A chiama B
 B chiama B1
 B1 chiama B2
 ……………..
 Bn chiama A
3
Ricorsione

In una definizione ricorsiva si definisce una


classe di oggetti strettamente correlati tra loro nei
termini degli oggetti stessi.
La definizione ricorsiva prevede
 una base: si definiscono uno o più oggetti semplici
 un passo di induzione: si definiscono oggetti più
grandi nei termini di quelli più piccoli della classe
Esempio 1: Fattoriale
algoritmo fact(intero n)  intero
n! = n × (n-1)! n1 if (n≤1) then return 1
else return n × fact(n-1)
0! =1 4
Esempio 2: Il problema dei conigli

Leonardo da Pisa (anche noto come Fibonacci) si


interessò di molte cose, tra cui il seguente
problema di dinamica delle popolazioni:
Quanto velocemente si espanderebbe una
popolazione di conigli sotto appropriate
condizioni?
In particolare, partendo da una coppia di conigli
in un’isola deserta, quante coppie si avrebbero
nell’anno n?
5
Esempio 2: Il problema dei conigli

All’anno 1 vi è una coppia di conigli neonati


All’anno successivo la coppia inizia il processo
di riproduzione
 ogni coppia genera una nuova coppia ogni anno
 ogni coppia non è in grado di procreare durante il
primo anno di vita
 i conigli sono immortali.

Quante coppie vi sono all’anno n, Fn?

6
La riproduzione dei conigli

All’anno 1 vi è 1 coppia: F1 = 1
All’anno 2 la coppia non può procreare: F2 = 1
All’anno 3 la coppia può procreare: F3 = 2
All’anno 4 l’ultima coppia nata non è fertile, quindi solo
la coppia originale si riproduce: F4 = 3

All’anno n vi sono le coppie presenti all’anno


precedente, Fn-1 , più quelle generate nell’ultimo anno
(ovvero le coppie fertili all’anno n-1), Fn-2
Fn = Fn-1 + Fn-2 con F1 = 1 e F2 = 1
o F0 = 0 e F1 = 1
7
L’albero dei conigli

La riproduzione dei conigli può essere descritta in un albero come


segue:

8
Algoritmo per il problema dei conigli

I primi numeri della successione di Fibonacci:


F0 F1 F2 F3 F4 F5 F6 F7 F8 …
0 1 1 2 3 5 8 13 21 ...

algoritmo fib(intero n)  intero


if (n=0) then return 0
else if (n=1) then return 1
else return fib(n-1) + fib(n-2)

9
Albero della ricorsione

Utile per risolvere la relazione di ricorrenza


Nodi corrispondenti alle chiamate ricorsive
Figli di un nodo corrispondenti alle sottochiamate

10
La ricerca binaria iterativa

algoritmo ricercaBinariaIter(array lista, elemento numric) intero

primo  0
ultimo  lunghezza di lista - 1

while (primo ≤ ultimo) do


mezzo  (primo+ultimo)/2
if (numric < lista[mezzo]) then ultimo  mezzo-1
else if (numric = lista[mezzo]) then return mezzo
else primo  mezzo+1
return -1

11
La ricerca binaria ricorsiva

algoritmo ricBinRic(array lista, elemento numric, intero primo, intero ultimo) intero

if (primo > ultimo) then return -1


mezzo  (primo + ultimo)/2
if (lista[mezzo] = numric) then return mezzo
else if (lista[mezzo] < numric) then return ricBinRic(lista, numric, mezzo + 1, ultimo)
else return ricBinRic(lista, numric, primo, mezzo -1)

12
Divide-et-impera

Nella ricorsione un problema si ripropone al suo interno in


sottoproblemi uguali all’originale, ma applicati a sottoinsiemi dei
dati.
La soluzione globale si ottiene come combinazione delle soluzioni
dei sottoproblemi (es. Fibonacci).

Il metodo divide-et-impera consiste nei seguenti passi


1. DIVIDI il problema in un certo numero di sottoproblemi;
2. CONQUISTA i sottoproblemi risolvendoli ricorsivamente;
3. COMBINA insieme le soluzioni dei sottoproblemi in una unica soluzione.

13
Divide-et-impera: schema

procedura Divide-et-Impera(P, intero n)

if (n ≤ k) then
risolvi P direttamente
else
dividi P in h sottoproblemi P1, …, Ph di dimensione n1, …, nh
for i  1 to h do Divide-et-Impera(Pi, ni)
combina i risultati di P1, …, Ph per ottenere quello di P

Se i dati sono partizionati in maniera bilanciata, cioè tutti gli ni


sono all’incirca uguali, allora l’algoritmo può risultare molto
efficiente (es. Quicksort, Mergesort).

14
Ricorsione: pro e contro

Vantaggi
 facilità di formulazione
 eleganza
 compattezza
 correttezza
 calcolo della complessità
Svantaggi
 comprensibilità delle operazioni
 tempo di calcolo
15
Analisi di un algoritmo

Un problema può essere risolto da più di un


algoritmo
Quale scegliere?

Quello migliore!

16
Analisi di un algoritmo

Criteri per giudicare la qualità di un algoritmo


 semplicità
 chiarezza
 efficienza
 quantità di risorse usate
 tempo di esecuzione
 quantità di memoria

17
Analisi di un algoritmo

Fattori che influenzano il tempo di esecuzione


 architettura
 linguaggio di programmazione
 compilatore
 fattori esterni
 incidenza sulla velocità di esecuzione per un fattore
costante
Le analisi che faremo saranno tutte a meno di
fattori costanti

18
Analisi di un algoritmo

I dati del problema (le sue dimensioni!)


Si definisce dimensione dell’input una funzione
che associa ad ogni ingresso un numero naturale
che rappresenta intuitivamente la quantità di
informazione contenuta nel dato
 ordinamento: numero di oggetti da ordinare
 gestione dati: numero di dati da gestire
 problemi sui grafi: numero di archi e nodi del grafo

Dipende dalla rappresentazione dei dati (struttura


dati) 19
Analisi di un algoritmo

Definiamo il tempo di esecuzione o complessità in


tempo T(n) come il numero di operazioni
elementari eseguite su un input di dimensione n

Il calcolo del tempo esatto è problematico


Vogliamo prescindere dalla reale esecuzione
dell’algoritmo (calcolo teorico della complessità
in tempo)
E’ necessaria un’operazione di astrazione
20
Analisi di un algoritmo

Possiamo determinare il numero totale di passi


compiuti dal programma/algoritmo su un input di
dimensione n
 paragonare due programmi diversi su uno stesso input
 capire come cresce il tempo di calcolo al variare delle
caratteristiche dell’input

Cos’è un passo di un programma?


 linea di pseudocodice
 istruzione
21
Analisi di un algoritmo

Assegnare un costo astratto ad ogni passo dell’algoritmo


comprensivo di
 numero di operazioni aritmetiche elementari
 costo effettivo di ogni operazione elementare.
Determinare il numero di volte che viene eseguito
ciascun passo.

Ipotesi:
 una singola istruzione richiede un tempo di esecuzione costante
 istruzioni diverse richiedono tempi di esecuzione diversi.

22
Esempio: un problema di conteggio
Input
 Un intero N dove N  1.
Output
 Il numero di coppie ordinate (i, j) tali che i e j sono
interi e 1 i  j  N.

Esempio: Input N=4


 (1,1), (1,2), (1,3), (1,4), (2,2), (2,3), (2,4), (3,3), (3,4), (4,4)
 Output = 10

23
Algoritmo 1

algoritmo Count_1(intero N)  intero


1 sum  0 1

2 for i  1 to N 2N
2  i 1 ( N  1  i )
N
3 for j  i to N


N
4 sum  sum + 1 i 1
( N  1  i)
5 return sum 1

3 2 7
Il tempo di esecuzione è 2  2 N  3i 1 ( N  1  i )  N  N  2
N

2 2
24
Algoritmo 2

algoritmo Count_2(intero N)  intero


1 sum  0 1
2 for i  1 to N 2N
3 sum  sum + (N + 1 ̶ i) 4N
4 return sum 1

Il tempo di esecuzione è 6N  2

 
N N
Osserviamo:
i 1
( N  1  i )  i 1
i  N ( N  1) / 2
25
Algoritmo 3

 
N N
i 1
( N  1  i )  i 1
i  N ( N  1) / 2

algoritmo Count_3(intero N)  intero


1 sum  N (N+1)/2 4
2 return sum 1

Il tempo di esecuzione è 5 unità di tempo

26
Riassumendo…

Tempo di
Algoritmo
Esecuzione
3 2 7
Count_1 N  N 2
2 2
Count_2 6N+2

Count_3 5

27
La complessità in pratica

Per n = 10 Per n = 100 Per n = 500


 T1(n) = 187  T1(n) = 15352  T1(n) = 376752
 T2(n) = 62  T2(n) = 602  T2(n) = 3002
 T3(n) = 5  T3(n) = 5  T3(n) = 5

Quale è l’algoritmo più conveniente?

28
La complessità in pratica

Per valutare la bontà di un algoritmo occorre


 effettuare una analisi asintotica della complessità in
tempo T(n), ovvero come cresce al crescere di n
(per n )
 esprimere T(n) in modo qualitativo
 perdere un po’ in precisione (senza perdere
l’essenziale) ma guadagnare in semplicità
 approssimativamente
ignorando costanti moltiplicative
ignorando termini di ordine inferiore.

29
Funzioni di complessità tipiche
Sia A1 di complessità in tempo n (lineare)
Sia A2 “ “ nlog2n (loglineare)
Sia A3 “ “ n2 (quadratica)
Sia A4 “ “ n3 (cubica)
Sia A5 “ “ 2n (esponenziale)
Sia A6 “ “ 3n (esponenziale)

Complessità Max dim T(operazione elementare)= 1sec


A1 n 6107
(10-6sec)
A2 nlog2n 28105
A3 n2 77102
A4 n3 390 Dimensioni massime di input processabili in
A5 2n 25 un minuto

30
La complessità al crescere di n

Comp n = 10 n = 20 n = 50 n = 100 n = 103 n = 104 n = 105 n = 106


Dim
n 10 s 20 s 50 s 0.1 ms 1 ms 10 ms 0.1 s 1s

nlog2n 33.2 s 86.4 s 0.28 ms 0.6 ms 9.9 ms 0.1 s 1.6 s 19.9 s

n2 0.1 ms 0.4 ms 2.5 ms 10 ms 1s 100 s 2.7 h 11.5 g

n3 1 ms 8 ms 125 ms 1s 16.6 m 11.5 g 31.7 a  300 c

2n 1 ms 1s 35.7 a 1014 c ••• ••• ••• •••

3n 59 ms 58 m 108 c ••• ••• ••• ••• •••

• • • > millennio
T(operazione elementare)= 1 sec (10-6 sec) 31
La complessità

Algoritmi di complessità nk (k 2) sono applicabili


sono per n non troppo elevato
 2  k < 3 applicabili su input di dimensione media
 k 3 tempi inaccettabili

Algoritmi di complessità lineare o quasi lineare (nlogn)


utilizzabili anche per input di dimensioni elevate

Algoritmi di complessità esponenziale hanno tempi di


calcolo proibitivi anche per input di dimensioni limitate

32
La notazione O-grande

Esprime il tempo di esecuzione in maniera


“approssimata”

Definizione
Siano f(n) e g(n) due funzioni definite in N e a valori in R

Diciamo che f(n) è O-grande di g(n) e scriviamo f(n) O(g(n))


(oppure f(n)= O(g(n)) ) se esistono una costante c>0 e un numero
n0N tale che n n0 si ha f(n)  c×g(n)

Si dice che f(n) ha ordine di grandezza minore o uguale a quello di


g(n)
33
La notazione O-grande
g(n) rappresenta il limite superiore asintotico a f(n)
(a meno di un fattore costante)
c g(n)

f(n)

g(n)

n0 34
Limite superiore asintotico: esempio

3n2 + 5  O(n2)
4g(n)=4n2
4 g(n) = 4n2
= 3n2 + n2
 3n2 + 9 per ogni n  3 f(n)=3n2+5
> 3n2 + 5
= f(n)
Quindi, f(n)  O(g(n))

g(n)=n2

3 35
Limite superiore asintotico: esempio

Mostrare che 3n2+2n+5  O(n2) 6


x 10
10

10n2 = 3n2 + 2n2 + 5n2 8

 3n2 + 2n + 5 7
10n2
6

5
c = 10, n0 = 1
4

3
3n2+2n+5
2

0
0 100 200 300 400 500 600 700 800 900 1000
36
Limite superiore asintotico: esempio

Mostrare che 2n2+3n+5  O(n2)


6
x 10
4

2n2 + 3n + 5  4n2 3.5

c = 4, n0 = 3 4n2
2.5

1.5

1
2n2+3n+5
0.5

0
0 100 200 300 400 500 600 700 800 900 1000
37
Utilizzo della notazione O

In genere quando impieghiamo la notazione O,


utilizziamo la formula più “semplice”.
 Scriviamo
 3n2+2n+5 = O(n2)

 Le seguenti sono tutte corrette ma in genere non le si


usa:
 3n2+2n+5 = O(3n2+2n+5)
 3n2+2n+5 = O(n2+n)
 3n2+2n+5 = O(3n2)

38
La notazione Omega-grande

Esprime il tempo di esecuzione in maniera “approssimata”

Definizione
Siano f(n) e g(n) due funzioni definite in N e a valori in R

Diciamo che f(n) è -grande di g(n) e scriviamo f(n) (g(n))


se esistono una costante c>0 e un numero n0N tale che
nn0 si ha f(n)  c×g(n)

39
La notazione Omega-grande
g(n) rappresenta il limite inferiore asintotico a f(n)
(a meno di un fattore costante)

f(n)

c g(n)

n0 40
Limite inferiore asintotico: esempio

g(n)/4 = n2/4
= n2/2 – n2/4
 n2/2 – 9 per tutti gli n  6 g(n)=n2
< n2/2 – 7
Quindi, f(n) (g(n))
f(n)=n2/2-7

c g(n)=n2/4

6 41
Notazione O e  : esempio

Mostrare che 3n+2  O(n) e 3n+2  (n)


4000

3n + 2  4n 3500
4n
3000
c = 4, n0 = 2
2500

3n+2
3n + 2  3n 2000

1500 3n
c = 3, n0 = 1
1000

500

0
0 100 200 300 400 500 600 700 800 900 1000
42
La notazione Theta-grande

Definizione
Siano f(n) e g(n) due funzioni definite in N e a valori in R

Diciamo che f(n) è -grande di g(n) e scriviamo f(n) (g(n))


se esistono due costanti c1>0, c2>0 e un numero n0N tale che
nn0 si ha c1×g(n)  f(n)  c2×g(n)

43
La notazione Theta-grande
g(n) è detto un limite asintotico stretto di f(n)

c2 g(n)

f(n)

c1 g(n)

n0
44
In sintesi

O : O-grande, limite asintotico superiore


 : Omega-grande, limite asintotico inferiore
 : Theta-grande, limite asintotico stretto
Usiamo la notazione asintotica per dare un limite
ad una funzione (f(n)), a meno di un fattore
costante (c).

45
La complessità in spazio

Definiamo la complessità in spazio come il


massimo spazio invaso nella memoria durante
l’esecuzione dell’algoritmo

Si studia la complessità asintotica, limitandosi al


suo ordine di grandezza
Ciò che definisce la bontà di un algoritmo è la
complessità in tempo

46
La complessità in spazio

Algoritmo non ricorsivo


 dipende dalla memoria allocata
 variabili, array, matrici, strutture dati, ecc.

Algoritmo ricorsivo
 dipende dalla memoria allocata da ogni chiamata e dal numero
di chiamate che sono contemporaneamente attive.

Una chiamata usa sempre almeno memoria costante


(anche se non utilizza variabili ausiliarie)
Analizzare l’albero della ricorsione aiuta a capire le
chiamate che possono essere attive nello stesso momento.
47
Caso medio, pessimo, ottimo

Per complessità media si intende la complessità di un


algoritmo mediato su tutte le possibili occorrenze iniziali
dei dati (difficile!)
Per complessità nel caso pessimo si intende la
complessità relativa a quella particolare occorrenza
iniziale dei dati per cui l’algoritmo ha comportamento
pessimo
 fornisce un limite superiore alla complessità
 semplice da individuare

La complessità nel caso ottimo non ci dice nulla sulla


bontà di un algoritmo
48
Riassumendo …

Ricorsione
Analisi degli algoritmi
Complessità in tempo e in spazio, notazioni O, 
e.

La prossima volta impareremo a valutare la


complessità di un algoritmo, ricorsivo o iterativo.

49