Due esempi di funzioni di Python: 1. nel primo mettiamo in evidenza

Analisi asintotica della
complessità di tempo
degli algoritmi
Due esempi di funzioni di Python:
1. nel primo mettiamo in evidenza l’importanza di una buona
organizzazione dei dati in memoria, cioè di una buona
struttura dati.
2.nel secondo esibiamo un conteggio per la valutazione della
complessità di tempo
L’esempio di un semplice problema risolto con due algoritmi
diversi ci servirà per giustificare l’introduzione di strumenti
matematici per semplificare il metodo di calcolo della
complessità.
1
Prof. E. Fachini - Intr. Alg.
Esempio 1
Quanto tempo ci vuole per accedere
all’elemento di indice i di una lista in Python?
Per esempio se la lista è
Numeri = [1,2,3,5,7,11,17,123]
Numeri[4] = 7
Vorremmo stabilire quanto tempo è necessario per ottenere la
risposta qualunque sia i, tra 0 e n-1, se la lista ha n elementi.
Vorremmo prevedere la risposta per ogni tipo di calcolatore.
Non vogliamo calcolare esattamente un valore ma piuttosto
sapere come il tempo dipende da n, il numero degli elementi della
lista o dal loro tipo, se non è costante.
2
Prof. E. Fachini - Intr. Alg.
Esempio 1
Quanto tempo ci vuole per accedere
all’elemento di indice i di una lista in Python?
Per esempio se la lista è
Numeri = [1,2,3,5,7,11,17,123]
Numeri[4] = 7
Vorremmo stabilire quanto tempo è necessario per ottenere la
risposta qualunque sia i, tra 0 e n-1, se la lista ha n elementi.
Cosa bisogna sapere per rispondere?
Come è implementata l’operazione e quindi come sono
memorizzati gli elementi!
3
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 1
Ricordiamo che il nostro modello prevede una memoria RAM.
In questo modello come è rappresentata in memoria la lista di Python?
Supponiamo per ora che la lista contenga
solo interi e che un intero sia rappresentato
in memoria in una parola di memoria, w.
Allora una lista, L, di interi potrebbe
occupare un’area contigua di memoria, a
partire da un indirizzo iniziale associato a L.
L’indirizzo dell’ elemento di indice i tra 0 e n-1,
si trova calcolando
ind(i) = indirizzo iniziale + i*|w|
dove|w| indica la lunghezza della parola w
Esempio
ListaInteri = [30,8,23,500]
1000
1032
1064
1096
Nel nostro esempio, ListaInteri contiene l’indirizzo di
partenza, 1000.
Il suo terzo elemento (che ha indice 2) sarà allocato alla
posizione 1000+2*32=1064
quindi L[2] = contenuto dell’indirizzo 1000+2*32
30
8
23
500
tempo
costante!
4
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 2
Ma una lista in Python può contenere dati di tipo diverso, che occupano
spazi di memoria diversi. Per esempio tra gli elementi ci può essere una
stringa o un’altra lista:
Numeri = [1,2,3,5,7,11,17,123]
Varia = [universitari,n°,5,Numeri]
universitari
0
n°
1
5
2
Numeri
3
inizio
L’elemento di indice i si trova calcolando
ind(i)=?
5
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 3
Si potrebbe usare una lista concatenata (linkata):
si dota ogni elemento della lista di un campo riferimento, che contiene
l’indirizzo dell’elemento successivo:
universitari
n°
0
1
5
Numeri
3
2
inizio
universitari a
inizio
n° b
a
5
c
b
Numeri
c
-
nell’esempio inizio,
accessibile dal nome della
lista, è l’indirizzo iniziale
dell’area di memoria che
contiene la stringa
“universitari” e il riferimento
a; a è l’indirizzo per la
stringa “n°” e del riferimento
b; b è quello per 5 e il
riferimento c; c è l’indirizzo
dell’area di memoria che
contiene l’indirizzo iniziale
nella lista Numeri e il
riferimento NULL (NIL,none),
cioè riferimento nullo,
perché non ci sono altri
elementi nella lista.
6
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 4
universitari a
inizio
n° b
a
5
b
c
Numeri
-
c
l’indirizzo dell’ elemento di indice i può essere ottenuto “seguendo” il riferimento
(l’indirizzo) contenuto nel campo riferimento dell’ elemento di indice (i-1).
Quindi per ottenerlo bisogna “seguire” i riferimenti, a partire dal primo, contenuto
nel nome della lista!
Dunque il numero di passi necessari non è costante, ma dipende dal valore di i,
che varia tra 0 e n-1.
7
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 5
Si potrebbe usare una lista di riferimenti, cioè una lista i cui elementi sono gli
indirizzi delle locazioni di memoria degli elementi. Il nome della lista, Varia, dà
l’accesso all’indirizzo inizio.
universitari
0
1
inizio
inizio =
n°
1000
1032
1064
1096
5
2
Numeri
3
3000
5
…
8000
9000
3000
5000
Numeri =
5000
8000
Un indirizzo occupa una parola di memoria
(nell’esempio di lunghezza 32), quindi l’elemento di
indice i nella lista è contenuto nella locazione di
memoria il cui indirizzo è in inizio+i*32.
Per esempio 5 è l’elemento di indice 2, quindi è
memorizzato nella parola di memoria il cui indirizzo
9000
è inizio+2*32 =1064, che è 3000.
2000
2032
2064
2096
…
……
u
n
…
r
i
…
n
°
8
Prof. E. Fachini - Intr. Alg.
Rappresentazione in memoria 5
Si potrebbe allora usare una lista di riferimenti, cioè una lista i
cui elementi sono gli indirizzi delle locazioni di memoria degli
elementi. Il nome della lista, Varia, dà l’accesso all’indirizzo
inizio.
universitari
0
n°
1
5
2
Numeri
3
inizio
inizio =
1000
1032
1064
1096
tempo
costante!
3000
5
…
8000
9000
3000
5000
Numeri =
5000
2000
2032
2064
2096
…
8000
Se si vuole accedere all’elemento di indice 2
della lista numeri, che è l’elemento di indice 3
della lista varia, andiamo al contenuto
all’indirizzo inizio + 3*32 (=5000). Poi
l’elemento di indice 2 è all’indirizzo 2000 +
2*32 (=2032).
9000
u
n
…
r
i
…
n
°
9
Prof. E. Fachini - Intr. Alg.
Conclusione
Questo esempio ci aiuta a capire che per stabilire, su un
qualsiasi computer, il tempo di esecuzione di
un’operazione bisogna innanzi tutto organizzare bene in
memoria i dati su cui lavora l’operazione, o le operazioni!
10
Prof. E. Fachini - Intr. Alg.
Tempo costante e no
Ordinare 30 numeri o ordinarne 3 milioni fa molta
differenza, in tal caso il tempo non è costante, ma
dipende dalla dimensione dell’input, cioè, in questo caso,
dal numero degli elementi da ordinare.
Intuitivamente il numero di esecuzioni delle operazioni
primitive può crescere con il crescere della dimensione
dell’input.
Consideriamo un altro esempio in Python.
11
Prof. E. Fachini - Intr. Alg.
L’inserimento in una lista?
Il metodo nome_lista.insert(where,el) inserisce l’ elemento el nella posizione
where nella lista nome_lista. Quindi where è l'indice del primo elemento che
verrà spostato nella posizione successiva, per poter inserire el. E’ eseguito in
un tempo costante o dipendente dalla dimensione (=numero degli elementi)
della lista?
...
0
1
2
n-3
n-2
n-1
inizio
...
Occorrerà (a parte i controlli sui valori della chiamata, di costo costante)
1. aumentare di uno la lunghezza, n, della lista e allocare nuova memoria per la
lista, se possibile
- tempo che può essere considerato costante
12
Prof. E. Fachini - Intr. Alg.
L’inserimento in una lista?
Il metodo nome_lista.insert(where,el) inserisce l’ elemento el nella posizione
where nella lista nome_lista. Quindi where è l'indice del primo elemento che verrà
spostato nella posizione successiva, per poter inserire el. E’ eseguito in un tempo
costante o dipendente dalla dimensione (=numero degli elementi) della lista?
...
0
1
2
n-3
n-2
n-1
inizio
...
2. spostare a destra di una posizione tutti i riferimenti da where a n-1. Ogni
spostamento ha un costo costante, ma ne devo fare n-1-where +1 = n-where.
- tempo dipendente da where, che può essere 0 o n-1
13
Prof. E. Fachini - Intr. Alg.
L’inserimento in una lista?
Il metodo nome_lista.insert(where,el) inserisce l’ elemento el nella posizione
where nella lista nome_lista. Quindi where è l'indice del primo elemento che verrà
spostato nella posizione successiva, per poter inserire el. E’ eseguito in un tempo
costante o dipendente dalla dimensione (=numero degli elementi) della lista?
...
0
1
2
n-3
n-2
n-1
inizio
...
3. inserire l’indirizzo di el in where
- tempo costante
14
Prof. E. Fachini - Intr. Alg.
Analisi inserimento in una lista
Il metodo nome_lista.insert(where,el) inserisce l’ elemento el nella
posizione where nella lista nome_lista.
I passi sono:
1. aumentare di uno la lunghezza, n, della lista e allocare nuova memoria
per la lista, se possibile - tempo che possiamo considerare costante
2. spostare a destra di una posizione tutti i puntatori da where a n tempo dipendente da where, che può essere 0 o n-1
3. inserire el in where - tempo costante
Possiamo dire che
–nel caso peggiore where = 0 e allora l’operazione è eseguita in c * n + d
passi per un certo c > 0, che dà conto del costo costante dell’ operazione di
spostamento a destra dei riferimenti che è eseguita n volte e d > 0 che dà
conto del costo costante delle operazioni ai punti 1 e 3.
–nel caso migliore where = n-1 e allora l’operazione è eseguita con c + d
passi
– in generale che l’operazione è eseguita in un numero di passi superiormente
limitato da c * n + d
15
Prof. E. Fachini - Intr. Alg.
Analisi inserimento in una lista
Il metodo nome_lista.insert(where,el) inserisce l’ elemento el nella posizione
where nella lista nome_lista.
Visto che
–nel caso peggiore where = 0 e allora l’operazione è eseguita in c * n + d
passi per un certo c > 0, che dà conto del costo costante dell’ operazione di
spostamento a destra dei riferimenti che è eseguita n volte e d > 0 che dà
conto del costo costante delle operazioni ai punti 1 e 3.
–nel caso migliore where = n-1 e allora l’operazione è eseguita con c + d
passi
– in generale che l’operazione è eseguita in un numero di passi
superiormente limitato da c * n + d
Se chiamiamo Tins(n) la funzione che esprime il tempo di calcolo del metodo
su un input di dimensione n, possiamo dire che Tins(n) ≤ c * n + d.
Oppure se chiamiamo TMAXins(n) la funzione che esprime il tempo di calcolo
del metodo nel caso peggiore su un input di dimensione n, possiamo dire
che TMAXins(n)= c * n + d.
16
Prof. E. Fachini - Intr. Alg.
L’inserimento in una lista,il codice in C:
http://svn.python.org/view/*checkout*/python/tags/r271/Objects/listobject.c?content-type=text%2Fplain
L’assegnamento
items[i+1] = items[i] è ripetuto
per i da n fino a where, cioè per
n - where volte.
L’istruzione for è eseguita ancora
una volta per stabilire che i è
diventato where-1 e quindi
determinare l’uscita dal ciclo.
Quindi detto a il costo
dell’assegnamento e dei
confronti, si ha un costo di
esecuzione del ciclo for minore o
uguale a a(n - where +1)
Quindi
TMAXins(n) ≤ c * n + d, dove d è il
costo costante delle operazioni
all’esterno del ciclo e c è
opportunamente scelta in modo
da maggiorare, semplificandola,
l’espressione a(n - 0 +1).
static int
ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
Py_ssize_t i, n = Py_SIZE(self);
PyObject **items;
if (v == NULL) {
PyErr_BadInternalCall();
return -1;
}
if (n == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more objects to list");
return -1;
}
if (list_resize(self, n+1) == -1)
return -1;
if (where < 0) {
where += n;
if (where < 0)
where = 0;
}
if (where > n)
where = n;
items = self->ob_item;
for (i = n; --i >= where; )
items[i+1] = items[i];
Py_INCREF(v);
items[where] = v;
return 0;
}
Prof. E. Fachini - Intr. Alg.
tempo
costante!
17
CONCLUSIONE
L’esempio ci aiuta a capire che per analizzare un algoritmo
abbiamo bisogno di considerare diversi casi, a parità di
dimensione dell’input.
In molti casi possiamo solo limitare superiormente il tempo di
calcolo di un algoritmo per una certa dimensione dell’ input,
considerando il caso peggiore per quella dimensione.
Prof. E. Fachini - Intr. Alg.
18
Altro esempio di analisi della complessità:
Inserimento di un elemento in una lista ordinata
Data una lista A di interi ordinata in ordine crescente, cioè in modo tale
che A[i] ≤ A[i+1], per 0≤i<n-1, vogliamo inserire un elemento, in modo da
ottenere ancora una lista di interi ordinata crescente.
Esempio: inserimento di 80 in :
20
40
70
0
1
2
200
4
400
5
700
6
19
Prof. E. Fachini - Intr. Alg.
Inserimento in un array ordinato: soluzione 1
Data una lista A di interi ordinata in ordine crescente, cioè in modo tale
che A[i] ≤ A[i+1], per 0≤i<n-1, vogliamo inserire un elemento, in modo da
ottenere ancora una lista di interi ordinata crescente.
Usiamo la stessa idea dell’inserimento di un elemento in una posizione di una
lista Python, con la differenza che la posizione va determinata sulla base
dell’ordine.
Diamo una funzione in cui assumiamo che l’elemento da inserire sia posto
nell’ultima posizione.
Esempio: inserimento di 80 nell’array:
20
40
70
0
1
2
200
4
400
5
700
6
7
80
20
Prof. E. Fachini - Intr. Alg.
Inserimento in un array ordinato: soluzione 1
Data una lista A di interi ordinata in ordine crescente tranne l’ultimo elemento, cioè in
modo tale che A[i] ≤ A[i+1], per 0≤i<n-2, vogliamo inserire l’ultimo elemento in modo
da ottenere una lista di interi ordinata crescente di tutti gli elementi.
Esempio: inserimento di 80 nel posto giusto nell’array:
20
40
70
0
1
2
200
400
80
x = 80
A[5] > x
3
4
5
6
400
700
700
20
40
70
200
0
1
2
3
20
40
70
200
0
1
2
20
40
70
0
1
2
3
4
20
40
70
80
200
0
1
2
3
4
Prof. E. Fachini - Intr. Alg.
700
3
200
4
400
4
200
5
400
5
400
5
400
5
6
A[4] > x
700
6
A[3] > x
700
6
A[2] ≤ x
700
6
21
Inserimento in un array ordinato: soluzione 1
Data una lista A di interi ordinata in ordine crescente tranne l’ultimo elemento, cioè in
modo tale che A[i] ≤ A[i+1], per 0≤i<n-2, vogliamo inserire l’ultimo elemento in modo
da ottenere una lista di interi ordinata crescente di tutti gli elementi.
Esempio: inserimento di 80 nel posto giusto nell’array:
20
40
70
0
1
2
200
4
400
5
InsOrd(A,lo,hi)
input:A è una lista di interi, e A[hi] è l’elemento da
inserire tra gli elementi A[lo],...,A[hi-1]
prec: A[lo:hi-1] è ordinato
post: l’array A[lo:hi] è ordinato
x = A[hi]
i = hi-1
while (i ≥ lo and x < A[i])
{A[i+1] = A[i]
// spostamento di una posizione a destra
i=i-1}
A[i+1] = x
700
6
80
7
// Se all’uscita i = lo - 1, vuol dire
che il controllo x < A[i] è risultato
vero per i = hi-1 fino a lo, quindi x
viene correttamente inserito in
A[lo].
Se l’uscita è determinata da x ≥
A[i], allora, x < A[j] per i < j ≤ hi-1 e,
poichè A è ordinato, tutti quelli che
precedono A[i] sono minori o
uguali di x mentre i successivi
sono più grandi. Quindi i+1 è il
posto giusto per x in A.
22
Prof. E. Fachini - Intr. Alg.
Inserimento in un array ordinato: complessità
soluzione 1
InsOrd(A,lo,hi)
input:A è una lista di interi, e A[hi] è l’elemento da inserire tra gli
elementi A[lo],...,A[hi-1]
prec: A[lo:hi-1] è ordinato
post: l’array A[lo:hi] è ordinato
x = A[hi]
i = hi-1
while (i ≥ lo and x < A[i])
{A[i+1] = A[i] // spostamento di una posizione a destra
i=i-1}
A[i+1] = x
Gli assegnamenti sono ripetuti finchè vale i ≥ lo e x<A[i].
Chiamiamo n il numero di elementi di A[lo:hi], c il costo (costante e
positivo) dei confronti e degli assegnamenti all’interno del ciclo while
e d quello (costante e positivo) delle operazioni al di fuori del ciclo.
23
Prof. E. Fachini - Intr. Alg.
Inserimento in un array ordinato: complessità
soluzione 1
InsOrd(A,lo,hi)
input:A è una lista di interi, e A[hi] è l’elemento da inserire tra gli elementi
A[lo],...,A[hi-1]
prec: A[lo:hi-1] è ordinato
post: l’array A[lo:hi] è ordinato
x = A[hi]
i = hi-1
while (i ≥ lo and x < A[i])
{A[i+1] = A[i] // spostamento di una posizione a destra
i=i-1}
A[i+1] = x
1. nel caso migliore non si entra nel ciclo while perché x ≥ A[hi-1], x si trova
già nella posizione giusta e allora tutta l’operazione è eseguita in tempo
costante.
2. nel caso peggiore x < A[lo] e allora le operazioni all’interno del ciclo
sono eseguite n volte, quindi la complessità di tempo nel caso peggiore su
un input di dimensione n è
TMAXInsOrd(n) = c * n + d.
3. in generale l’operazione su un input di n elementi è eseguita in un numero
di passi superiormente limitato da c * n + d, cioè TInsOrd(n) ≤ c * n + d. 24
Prof. E. Fachini - Intr. Alg.
Inserimento in un array ordinato: soluzione 2
Data una lista A di interi ordinata in ordine crescente, cioè in
modo tale che A[i] ≤ A[i+1], per 0≤i<n-1, vogliamo inserire un
elemento, in modo da ottenere ancora una lista di interi
ordinata crescente.
Possiamo usare l’algoritmo della ricerca binaria o dicotomica, per
determinare la posizione di inserimento dell’elemento.
Una volta determinata la corretta posizione, i, possiamo inserire l’elemento
spostando a destra di una posizione tutti gli elementi dall’i-simo all’ultimo.
25
Prof. E. Fachini - Intr. Alg.
Algoritmo della ricerca binaria o dicotomica, per determinare la
posizione di inserimento di un elemento in un array ordinato.
RBisect(A,k)
INPUT: una sequenza A di elementi e l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è il numero degli elementi
OUTPUT: restituisce la posizione nella quale inserire k se non occorre nella
sequenza, l’indice dell’elemento immediatamente a destra dell’ultima
occorrenza di k altrimenti.
sia lo = 0 e hi = len(A) %inizialmente k può essere ovunque nel vettore
%nel ciclo che segue l’intervallo della ricerca è sempre determinato dagli
indici lo,…,hi-1
finchè ci sono elementi da esaminare
sia m l’indice dell’elemento centrale in A[lo],…,A[hi]
se k < A[m] poni hi=m %cerca k tra gli elementi di indice lo,…,m-1,
se k ≥ A[m] poni lo=m+1 %cerca k tra gli elementi di indice m+1,…,hi-1
return lo
26
Prof. E. Fachini - Intr. Alg.
RBisect: dettagli
RBisect(A,k)
INPUT: una lista A di interi e
l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è
il numero degli elementi
OUTPUT: restituisce la posizione
nella quale inserire k se non
occorre nella sequenza, l’indice
dell’elemento immediatamente a
destra dell’ultima occorrenza di
k altrimenti.
1. sia lo = 0 e hi = len(A)
2. finchè ci sono elementi da
esaminare
%nel ciclo che segue l’intervallo
della ricerca è sempre
determinato dagli indici lo,
…,hi-1
3. sia m l’indice dell’elemento
centrale in A[lo],…,A[hi]
4. se k < A[m] poni hi=m
5. se k ≥ A[m] poni lo=m+1
6. return lo
Prof. E. Fachini - Intr. Alg.
Quando non ci sono più elementi da
esaminare?
Quando lo=hi, visto che gli elementi su
cui si fa la ricerca sono quelli di indice
compreso tra lo e hi-1.
Come calcolo l’indice dell’elemento
centrale?
Se si pone m = (hi-1 - lo +1)/2, perché
non va bene?
Perché essendo lo l’indice di partenza,
per trovare il punto centrale
nell’intervallo [lo,hi-1] devo aggiungerlo
alla metà del numero degli elementi.
Quindi
m = lo + (hi-lo)/2 oppure
m = (lo + hi)/2 se si è sicuri di non
27
incorrere in overflow
RBisect: Esempio
RBisect(A,k)
INPUT: una lista A di interi e
l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è
il numero degli elementi
OUTPUT: restituisce la posizione
nella quale inserire k se non
occorre nella sequenza, l’indice
dell’elemento immediatamente a
destra dell’ultima occorrenza di
k altrimenti.
1. lo = 0 e hi = len(A)
2. finchè lo < hi
%nel ciclo che segue l’intervallo
della ricerca è sempre
determinato dagli indici lo,
…,hi-1
3. m = lo + (hi-lo)/2
4. se k < A[m] poni hi=m
5. se k ≥ A[m] poni lo=m+1
6. return lo
lo=0,hi=7
m=3 e key=4<8
lo=0,hi=3
m=1 e key≥4
lo=2,hi =3 m=2 e key≥4
lo=3,hi=3 e uscita
2 4 4 8 10 13 17
0
1
2
3
4
5
6
28
Prof. E. Fachini - Intr. Alg.
RBisect: complessità 1
RBisect(A,k)
INPUT: una lista A di interi e l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è il numero degli elementi
OUTPUT: restituisce la posizione nella quale inserire k se non occorre nella
sequenza, l’indice dell’elemento immediatamente a destra dell’ultima
occorrenza di k altrimenti.
1. lo = 0 e hi = len(A)
2. finchè lo < hi
%nel ciclo che segue l’intervallo della ricerca è sempre determinato dagli indici
lo,…,hi-1
3. m = lo + (hi-lo)/2
4. se k < A[m] poni hi=m
5. se k ≥ A[m] poni lo=m+1
6. return lo
Le istruzioni dei punti 1,2,3,4,5,6 sono di costo costante.
Le istruzioni 1 e 6 sono eseguite solo una volta
Quante volte sono eseguite le istruzioni 2,3,4 e 5?
29
Prof. E. Fachini - Intr. Alg.
RBisect: complessità 2
RBisect(A,k)
INPUT: una lista A di interi e l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è il numero degli elementi
OUTPUT: restituisce la posizione nella quale inserire k se non occorre nella
sequenza, l’indice dell’elemento immediatamente a destra dell’ultima
occorrenza di k altrimenti.
1. lo = 0 e hi = len(A)
2. finchè lo < hi
%nel ciclo che segue l’intervallo della ricerca è sempre determinato dagli indici
lo,…,hi-1
3. m = lo + (hi-lo)/2
4. se k < A[m] poni hi=m
5. se k ≥ A[m] poni lo=m+1
6. return lo
All’inizio abbiamo n elementi nell’intervallo della ricerca, poi circa la metà, n/2,
poi ancora la metà n/4,…
Quante volte posso dimezzare n prima di arrivare ad avere un numero di elementi
minore o uguale a 1?
30
Prof. E. Fachini - Intr. Alg.
RBisect: complessità 3
Supponiamo n = 2k, allora dopo k divisioni per 2 si ottiene 1, e k = lg n,
avendo posto log2 n = lg n
Se 2k ≤ n < 2k+1, di nuovo dopo k divisioni per 2 si ottiene 1:
1 ≤ n/2k < 2k+1/2k = 2.
k è la parte intera inferiore del logaritmo in base 2 di n.
Quindi possiamo dire che le istruzioni all’interno del ciclo sono
eseguite circa lg n volte.
Chiamiamo n il numero di elementi di A, cr il costo (costante e positivo)
dei confronti e degli assegnamenti all’interno del ciclo while e dr
quello (costante e positivo) delle operazioni al di fuori del ciclo.
31
Prof. E. Fachini - Intr. Alg.
RBisect: complessità 4
RBisect(A,k)
INPUT: una lista A di interi e l’elemento da cercare, k.
PREC: A[0]≤A[1]≤...≤A[n-1], dove n è il numero degli elementi
OUTPUT: restituisce la posizione nella quale inserire k se non occorre nella sequenza, l’indice
dell’elemento immediatamente a destra dell’ultima occorrenza di k altrimenti.
1. lo = 0 e hi = len(A)
2. finchè lo < hi
%nel ciclo che segue l’intervallo della ricerca è sempre determinato dagli indici lo,…,hi-1
3. m = lo + (hi-lo)/2
4. se k < A[m] poni hi=m
5. se k ≥ A[m] poni lo=m+1
6. return lo
Quindi posso concludere che TRBisect(n) = TMAXRBisect(n) = cR * lg n + dR , per
opportune costanti cR,dR >0 (qui l’indice R in basso sta per Ricerca).
Qui infatti il numero di esecuzioni del ciclo è lo stesso su ogni input di n
elementi.
Tornando al problema dell’inserimento di un elemento in una lista
ordinata, a questa complessità va aggiunta quella per spostare gli
elementi di indice lo fino all’ultimo di una posizione a destra per poter
inserire k.
32
Prof. E. Fachini - Intr. Alg.
Confronto di complessità
Nel caso peggiore per l’ operazione di spostamento a destra e
inserimento si ha:
TMAXinsPos(n)=cI * n + dI , (dove I sta per Inserimento).
A TMAXinsPos(n) si deve sommare il costo della ricerca della posizione in
cui inserire l’elemento TMAXRBisect(n) = cR * lg n + dR
Per cui in definitiva
TMAXinsOrd2(n) = TMAXRBisect(n) + TMAXinsPos(n)= cI * n + dI + cR * lg n + dR
Mentre per la prima soluzione sempre nel caso peggiore:
TMAXInsOrd(n) = c * n + d
Siamo ora in grado di decidere quale dei due algoritmi è meglio?
Nel caso peggiore, con il primo algoritmo facciamo n confronti e n
assegnamenti per “fare spazio”, mentre con il secondo facciamo lg n
confronti e n spostamenti per “fare spazio”.
Le costanti invece dipendono dal particolare ambiente hardware/software
e non pesano sul tasso di crescita delle funzioni TMAX.
33
Prof. E. Fachini - Intr. Alg.
La crescita di una funzione lineare:
f (n) = 3n+ 4
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Prof. E. Fachini - Intr. Alg.
34
Lg n
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
g ( n ) = lg n
La funzione logaritmo cresce molto lentamente.
Prof. E. Fachini - Intr. Alg.
35
Confronto tra i due algoritmi
TMAXInsOrd(n) = c * n + d
TMAXinsOrd2(n) = TMAXRBisect(n) + TMAXinsPos(n)= cI * n + dI + cR * lg n + dR
Nel caso peggiore, con il primo algoritmo facciamo n confronti e n
assegnamenti per “fare spazio”, mentre con il secondo facciamo lg n
confronti e n spostamenti per “fare spazio”.
Intuitivamente, se i confronti sono molto costosi e n è molto grande è
preferibile l’algoritmo che ne fa di meno, altrimenti meglio il più semplice.
Ma in casi più complessi come regolarsi?
Introduciamo uno strumento che ci consenta di non dover considerare le
costanti, visto che non sono determinanti nella comprensione di come
cresce la funzione complessità di tempo in dipendenza dell’input.
36
Prof. E. Fachini - Intr. Alg.