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.