Scarica Algoritmi e Strutture Dati: Dispensa del Corso e più Appunti in PDF di Algoritmi E Strutture Di Dati solo su Docsity!
Dispense del Corso di
Algoritmi e Strutture Dati
Marco Bernardo Edoardo Bont`a
Universita degli Studi di Urbino “Carlo Bo” Facolta di Scienze e Tecnologie Corso di Laurea in Informatica Applicata
Versione del 30/11/
Queste dispense non sono in nessun modo sostitutive dei testi consigliati. Queste dispense sono state preparate con LATEX. ©c 2007
ii
- 1 Introduzione agli algoritmi e alle strutture dati
- 1.1 Algoritmi e loro tipologie
- 1.2 Correttezza di un algoritmo rispetto ad un problema
- 1.3 Complessit`a di un algoritmo rispetto all’uso di risorse
- 1.4 Strutture dati e loro tipologie
- 2 Classi di problemi
- 2.1 Problemi decidibili e indecidibili
- 2.2 Problemi trattabili e intrattabili
- 2.3 Teorema di Cook
- 2.4 N P-completezza
- 3 Complessit`a degli algoritmi
- 3.1 Notazioni per esprimere la complessit`a asintotica
- 3.2 Calcolo della complessit`a di algoritmi non ricorsivi
- 3.3 Calcolo della complessit`a di algoritmi ricorsivi
- 4 Algoritmi per array
- 4.1 Array: definizioni di base e problemi classici
- 4.2 Algoritmo di visita per array
- 4.3 Algoritmo di ricerca lineare per array
- 4.4 Algoritmo di ricerca binaria per array ordinati
- 4.5 Criteri di confronto per algoritmi di ordinamento per array
- 4.6 Insertsort
- 4.7 Selectsort
- 4.8 Bubblesort
- 4.9 Mergesort
- 4.10 Quicksort
- 4.11 Heapsort
- 5 Algoritmi per liste
- 5.1 Liste: definizioni di base e problemi classici
- 5.2 Algoritmi di visita, ricerca, inserimento e rimozione per liste
- 5.3 Algoritmi di inserimento e rimozione per code
- 5.4 Algoritmi di inserimento e rimozione per pile
- 6 Algoritmi per alberi
- 6.1 Alberi: definizioni di base e problemi classici
- 6.2 Algoritmi di visita e ricerca per alberi binari
- 6.3 Algoritmi di ricerca, inserimento e rimozione per alberi binari di ricerca
- 6.4 Criteri di bilanciamento per alberi binari di ricerca
- 6.5 Algoritmi di ricerca, inserimento e rimozione per alberi binari di ricerca rosso-nero
- 7 Algoritmi per grafi iv INDICE
- 7.1 Grafi: definizioni di base e problemi classici
- 7.2 Algoritmi di visita e ricerca per grafi
- 7.3 Algoritmo di ordinamento topologico per grafi diretti e aciclici
- 7.4 Algoritmo delle componenti fortemente connesse per grafi
- 7.5 Algoritmo di Kruskal
- 7.6 Algoritmo di Prim
- 7.7 Propriet
a del percorso piu breve - 7.8 Algoritmo di Bellman-Ford
- 7.9 Algoritmo di Dijkstra
- 7.10 Algoritmo di Floyd-Warshall
- 8 Tecniche algoritmiche
- 8.1 Tecnica del divide et impera
- 8.2 Programmazione dinamica
- 8.3 Tecnica golosa
- 8.4 Tecnica per tentativi e revoche
- 9 Correttezza degli algoritmi
- 9.1 Triple di Hoare
- 9.2 Determinazione della precondizione pi`u debole
- 9.3 Verifica della correttezza di algoritmi iterativi
- 9.4 Verifica della correttezza di algoritmi ricorsivi
- 10 Attivit`a di laboratorio
- 10.1 Valutazione sperimentale della complessit`a degli algoritmi
- 10.2 Confronto sperimentale degli algoritmi di ordinamento per array
- 10.3 Confronto sperimentale degli algoritmi di ricerca per alberi binari
Capitolo 1
Introduzione agli algoritmi e alle
strutture dati
1.1 Algoritmi e loro tipologie
- Il termine “algoritmo” deriva dal nome del matematico persiano Muhammad al-Khwarizmi (IX secolo), autore di uno dei pi`u antichi trattati di algebra in cui descrisse procedure per eseguire calcoli aritmetici.
- Un algoritmo `e una sequenza finita di passi interpretabili da un esecutore.
- Sebbene un algoritmo sia composto da un numero finito di passi, la sua esecuzione potrebbe richiedere un tempo non necessariamente finito.
- L’esecutore di un algoritmo non deve necessariamente essere un computer, ma pu`o essere un dispositivo meccanico, un circuito elettronico o un sistema biologico.
- Quand’anche l’esecutore fosse un computer, un algoritmo non deve necessariamente essere espresso in un linguaggio di programmazione, in quanto esso si trova comunque ad un livello di astrazione pi`u alto rispetto ad ogni programma che lo implementa.
- Un algoritmo non deve necessariamente essere espresso attraverso un supporto linguistico. A volte pu
o essere piu comodo rappresentare un algoritmo graficamente con un albero di decisione. - Esempio: dati una bilancia a due piatti e 12 monete dello stesso aspetto tra le quali al pi
u una potrebbe avere peso diverso dalle altre, la moneta diversa puo essere individuata, specificando se pesa di piu o di meno delle altre, mediante non piu di 3 pesate come descritto dal seguente albero di decisione (12 + 12 + 1 = 25 diversi esiti non possono essere gestiti con meno di 3 pesate)
1 : 2 < = > < > < = > < = > < > < = >
< = > < = > < = >
< = > < = >
1, 2, 3, 4 : 5, 6, 7, 8
1, 2, 5 : 3, 4, 6 9, 10 : 11, 1 1, 2, 5 : 3, 4, 6
7 : 8 3 : 4 3 : 4 7 : 8 1 : 2
1- 6+ 2- 8+ 7+ 3- 5+ 4- 4+ 5- 3+ 7- 8- 2+ 6- 1+
9- 11+ 10- 12- no 12+ 10+ 11- 9+
9 : 10 12 : 1 9 : 10
< = >
< = >
1.3 Complessit`a di un algoritmo rispetto all’uso di risorse 3
1.3 Complessit`a di un algoritmo rispetto all’uso di risorse
- Dato un problema, possono esistere pi`u algoritmi che sono corretti rispetto ad esso.
- Questi algoritmi possono essere confrontati rispetto alla loro complessit
a o efficienza computazionale, cioe rispetto alla quantit`a di uso che essi fanno delle seguenti risorse durante la loro esecuzione: - Tempo di calcolo. - Spazio di memoria. - Banda trasmissiva. - Poich´e tramite un’opportuna gerarchia di memorie
e possibile avere a disposizione una capacita di memoria praticamente illimitata e inoltre gli algoritmi che considereremo non richiedono lo scambio di dati tra computer, in questo corso ci concentreremo sulla complessit`a temporale degli algoritmi. - Un motivo pi
u profondo per concentrare l’attenzione sulla complessita temporale e che lo spazio e la banda occupati in un certo momento possono essere riusati in futuro (risorse recuperabili), mentre il passare del tempoe irreversibile (risorsa non recuperabile). - Dato un problema, `e sempre possibile trovare un algoritmo che lo risolve in maniera efficiente?
1.4 Strutture dati e loro tipologie
- C’`e uno stretto legame pure tra algoritmi e strutture dati, in quanto le strutture dati costituiscono gli ingredienti di base degli algoritmi.
- Anche l’efficienza di un algoritmo dipende in maniera critica dal modo in cui sono organizzati i dati su cui esso deve operare.
- Una struttura dati `e un insieme di dati logicamente correlati e opportunamente memorizzati, per i quali sono definiti degli operatori di costruzione, selezione e manipolazione.
- Le varie strutture dati sono riconducibili a combinazioni di strutture dati appartenenti alle quattro classi fondamentali che studieremo in questo corso: array, liste, alberi e grafi.
- Classificazione delle strutture dati basata sulla loro occupazione di memoria:
- Strutture dati statiche: la quantit
a di memoria di cui esse necessitanoe determinabile a priori (array). - Strutture dati dinamiche: la quantit
a di memoria di cui esse necessitano varia a tempo d’esecuzione e puo essere diversa da esecuzione a esecuzione (liste, alberi, grafi). flf 1
4 Introduzione agli algoritmi e alle strutture dati
6 Classi di problemi
2.2 Problemi trattabili e intrattabili
- I problemi decidibili vengono poi classificati in base alla loro trattabilit
a, cioe alla possibilit`a di risolverli in maniera efficiente. - Al fine di confrontare i problemi decidibili in modo equo rispetto alla loro trattabilit
a, conviene portarli tutti nella loro forma piu semplice di problemi di decisione. - Un problema di decisione decidibile
e intrinsecamente intrattabile se none risolvibile in tempo polino- miale nemmeno da un algoritmo non deterministico. - Per i problemi di decisione decidibili che non sono intrinsecamente intrattabili si usa adottare la seguente classificazione: - P
e l’insieme dei problemi di decisione risolvibili in tempo polinomiale da un algoritmo determi- nistico. - N Pe l’insieme dei problemi di decisione risolvibili in tempo polinomiale da un algoritmo non deterministico. Equivalentemente, esso puo essere definito come l’insieme dei problemi di decisione tali che la correttezza di una soluzione corrispondente ad un’istanza dei dati di ingresso puo essere verificata in tempo polinomiale da un algoritmo deterministico. - Ovviamente P ⊆ N P.
- L’inclusione di P in N P
e stretta oppure i due insiemi coincidono? In altri termini,e possibile risolvere in maniera efficiente ogni problema non intrinsecamente intrattabile?
2.3 Teorema di Cook
- Il teorema di Cook
e un risultato fondamentale nello studio della complessita della risoluzione dei problemi non intrinsecamente intrattabili. - Esso si basa sul concetto di riducibilit
a in tempo polinomiale tra problemi e individua nel problema della soddisfacibilita l’archetipo di problema della classe N P. - Dati due problemi di decisione P 1 ⊆ I 1 × { 0 , 1 } e P 2 ⊆ I 2 × { 0 , 1 }, diciamo che P 1 `e riducibile in tempo polinomiale a P 2 se esiste un algoritmo deterministico che calcola in tempo polinomiale una funzione f : I 1 −→ I 2 tale che per ogni i ∈ I 1 ed s ∈ { 0 , 1 } risulta: (i, s) ∈ P 1 ⇐⇒ (f (i), s) ∈ P 2
- La riducibilit
a in tempo polinomiale di P 1 a P 2 implica che la soluzione di ogni istanza di P 1 puo essere ottenuta risolvendo la corrispondente istanza di P 2 calcolabile in tempo polinomiale. - Problema della soddisfacibilit
a: data un’espressione logica in forma normale congiuntiva, stabilire se esiste un assegnamento di valori di verita alle sue variabili che la rende vera. - Teorema di Cook (1971): ogni problema della classe N P
e riducibile in tempo polinomiale al problema della soddisfacibilita. - Corollario: se esiste un algoritmo deterministico che risolve il problema della soddisfacibilit`a in tempo polinomiale, allora N P = P.
2.4 N P-completezza 7
2.4 N P-completezza
- Il problema di stabilire l’esatta relazione tra P ed N P `e ancora aperto, quindi non sappiamo dire se tutti i problemi non intrinsecamente intrattabili siano risolvibili in maniera efficiente oppure no.
- Nella pratica i problemi in N P per i quali non sono noti algoritmi deterministici che li risolvono in tempo polinomiale vengono affrontati tramite algoritmi di approssimazione. Questi sono algorit- mi deterministici che producono in tempo polinomiale una soluzione approssimata dei problemi in corrispondenza di ciascuna delle loro istanze dei dati di ingresso.
- Dal punto di vista teorico, sono stati individuati molti altri problemi ai quali si applica il corolla- rio al teorema di Cook perch´e sono tanto complessi computazionalmente quanto il problema della soddisfacibilit`a. Questi problemi sono chiamati N P-completi.
- Un problema in N P
e detto N P-completo se il problema della soddisfacibilita `e riducibile ad esso in tempo polinomiale. - Esempi di problemi N P-completi:
- Problema della soddisfacibilit`a.
- Problema dello zaino: dati un insieme A = {a 1 ,... , an} di interi positivi e due interi positivi c e z, stabilire se esiste un sottoinsieme di A i cui elementi abbiano somma compresa tra c e z.
- Problema delle scatole: dati un insieme A = {a 1 ,... , an} di interi positivi e due interi positivi k ed s, stabilire se esiste una partizione di A in k sottoinsiemi disgiunti A 1 ,... , Ak tale che la somma degli elementi in ogni Ai non supera s.
- Problema del sottografo completo: dati un grafo G e un intero positivo k, stabilire se G ha un sottografo completo di k vertici.
- Problema della colorazione di un grafo: dati un grafo G e un intero positivo k, stabilire se i vertici di G possono essere colorati con k colori in modo che non vi siano due vertici adiacenti dello stesso colore.
- Problema del commesso viaggiatore: dati un grafo G in cui ogni arco ha un costo intero positivo e un intero positivo k, stabilire se G contiene un ciclo che attraversa ciascuno dei suoi vertici una sola volta in cui la somma dei costi degli archi non supera k. flf 2
Capitolo 3
Complessit`a degli algoritmi
3.1 Notazioni per esprimere la complessit`a asintotica
- Gli algoritmi che risolvono lo stesso problema decidibile vengono confrontati sulla base della loro efficienza, misurata attraverso il loro tempo d’esecuzione.
- Il tempo d’esecuzione di un algoritmo viene espresso come una funzione della dimensione dei dati di ingresso, di solito denotata con T (n).
- La caratterizzazione della dimensione n dei dati di ingresso dipende dallo specifico problema. Pu
o essere il numero di dati di ingresso oppure la quantita di memoria necessaria per contenere tali dati. - Al fine di catturare l’intrinseca complessit
a di un algoritmo, il tempo d’esecuzione deve essere indi- pendente dalla tecnologia dell’esecutore, quindi non puo essere misurato attraverso il tempo di CPU impiegato su un certo computer dal programma che implementa l’algoritmo. - Il tempo d’esecuzione di un algoritmo
e dato dal numero di passi base compiuti durante l’esecuzione dell’algoritmo. Volendo formalizzare gli algoritmi nel linguaggio ANSI C, un passo basee costituito dall’esecuzione di un’istruzione di assegnamento (priva di chiamate di funzioni) o dalla valutazione di un’espressione (priva di chiamate di funzioni) contenuta in un’istruzione di selezione o ripetizione. - Il tempo d’esecuzione di un algoritmo pu`o essere calcolato in tre diversi casi:
- Caso pessimo:
e determinato dall’istanza dei dati di ingresso che massimizza il tempo d’esecuzione, quindi fornisce un limite superiore alla quantita di risorse computazionali necessarie all’algoritmo. - Caso ottimo:
e determinato dall’istanza dei dati di ingresso che minimizza il tempo d’esecuzione, quindi fornisce un limite inferiore alla quantita di risorse computazionali necessarie all’algoritmo. - Caso medio:
e determinato dalla somma dei tempi d’esecuzione di tutte le istanze dei dati di ingresso, con ogni addendo moltiplicato per la probabilita di occorrenza della relativa istanza dei dati di ingresso.
- Poich´e il confronto degli algoritmi che risolvono lo stesso problema decidibile si riduce a confrontare delle funzioni – le quali possono relazionarsi in modi diversi per istanze diverse dei dati di ingresso – di solito si ragiona in termini di complessit`a computazionale asintotica.
- Le funzioni tempo d’esecuzione dei vari algoritmi che risolvono lo stesso problema decidibile vengono dunque confrontate considerando il loro andamento al crescere della dimensione n dei dati di ingresso. Ci`o significa che, all’interno delle funzioni, si possono ignorare le costanti moltiplicative e i termini non dominanti al crescere di n.
10 Complessit`a degli algoritmi
- Notazioni per esprimere relazioni asintotiche tra funzioni intere di variabili intere:
- Limite asintotico superiore (intuitivamente, g(n) cresce al pi`u come f (n) per n → ∞): g(n) = O(f (n)) ⇐⇒ ∃c, n 0 > 0. ∀n ≥ n 0. g(n) ≤ c · f (n)
- Limite asintotico inferiore (intuitivamente, g(n) cresce almeno come f (n) per n → ∞): g(n) = Ω(f (n)) ⇐⇒ ∃c, n 0 > 0. ∀n ≥ n 0. g(n) ≥ c · f (n)
- Limite asintotico stretto (intuitivamente, g(n) cresce come f (n) per n → ∞): g(n) = Θ(f (n)) ⇐⇒ ∃c 1 , c 2 , n 0 > 0. ∀n ≥ n 0. c 1 · f (n) ≤ g(n) ≤ c 2 · f (n)
n 0
c f(n)^. c f(n).
n 0 n 0
c 2 .f(n)
n
g(n)
g(n)
n
g(n)
n
c 1 .f(n)
- Classi di complessit`a asintotica degli algoritmi:
- T (n) = O(1): complessit
a costante (cioe T (n) non dipende dalla dimensione n dei dati di ingresso). - T (n) = O(log n): complessit`a logaritmica.
- T (n) = O(n): complessit`a lineare.
- T (n) = O(n · log n): complessit
a pseudolineare (cosı detta da n · log n = O(n1+≤) per ogni ≤ > 0). - T (n) = O(n^2 ): complessit`a quadratica.
- T (n) = O(n^3 ): complessit`a cubica.
- T (n) = O(nk), k > 0: complessit`a polinomiale.
- T (n) = O(αn), α > 1: complessit`a esponenziale.
3.2 Calcolo della complessit`a di algoritmi non ricorsivi
- Il tempo d’esecuzione di un algoritmo non ricorsivo pu
o essere calcolato utilizzando le seguenti regole definite per induzione sulla struttura dell’algoritmo: - Il tempo d’esecuzione (risp. di valutazione) di un’istruzione di assegnamento x = e; (risp. di un’espressione e)e: T (n) =
1 = O(1) se priva di chiamate di funzioni T ′(n) + 1 = O(f ′(n)) se contiene chiamate di funzioni eseguibili in T ′(n) = O(f ′(n))
- Il tempo d’esecuzione di una sequenza S 1 S 2... Ss di s ≥ 2 istruzioni, ciascuna avente tempo d’esecuzione Ti(n) = O(fi(n)) per 1 ≤ i ≤ s, `e: T (n) =
∑s i=
Ti(n) = O(max{fi(n) | 1 ≤ i ≤ s})
- Il tempo d’esecuzione di un’istruzione di selezione if (β) S 1 else S 2 in cui: ∗ il tempo di valutazione dell’espressione β
e T 0 (n) = O(f 0 (n)); ∗ il tempo d’esecuzione dell’istruzione S 1e T 1 (n) = O(f 1 (n)); ∗ il tempo d’esecuzione dell’istruzione S 2 e T 2 (n) = O(f 2 (n));e: T (n) = T 0 (n) + op(T 1 (n), T 2 (n)) = O(max{f 0 (n), op(f 1 (n), f 2 (n))}) dove op `e max nel caso pessimo e min nel caso ottimo.
12 Complessit`a degli algoritmi
nel caso pessimo (n ≥ 3) ha complessita: T (n) = 1 + 1 + (n − 2) · (1 + 1 + 1 + 1 + 1) + 1 = 5 · n − 7 = O(n) mentre nel caso ottimo (n ≤ 2) ha complessita: T (n) = 1 + 1 = 2 = O(1)
- Il seguente algoritmo non ricorsivo per calcolare il massimo di un insieme di n ≥ 1 interi:
int calcola_max(int a[], int n) { int max, i;
for (max = a[0], i = 1; (i < n); i++) if (a[i] > max) max = a[i]; return(max); } nel caso pessimo (array ordinato rispetto a <) ha complessita: T (n) = 1 + (n − 1) · (1 + 1 + 1 + 1) + 1 = 4 · n − 2 = O(n) mentre nel caso ottimo (massimo contenuto in a[0]) ha complessita: T (n) = 1 + (n − 1) · (1 + 1 + 1) + 1 = 3 · n − 1 = O(n) Per il problema dato non e possibile trovare un algoritmo migliore di quello mostrato, in quanto si puo dimostrare che il problema in questione non puo essere risolto con un algoritmo di complessita asintotica inferiore a quella lineare. flf 3
3.3 Calcolo della complessit`a di algoritmi ricorsivi
- Un algoritmo ricorsivo ha la seguente struttura: soluzione risolvi(problema p) { soluzione s, s 1 , s 2 ,.. ., sn; if (/p semplice .) /calcola direttamente la soluzione s di p.; else { /dividi p in p 1 , p 2 ,.. ., pn della stessa natura di p.; s 1 = risolvi(p 1 ); s 2 = risolvi(p 2 );.. .; sn = risolvi(pn); /combina s 1 , s 2 ,.. ., sn per ottenere la soluzione s di p.; } return(s); }
- Il tempo d’esecuzione di un algoritmo ricorsivo non pu`o essere definito in forma chiusa attraverso le regole viste in precedenza, ma in modo induttivo attraverso una relazione di ricorrenza le cui incognite costituiscono una successione di numeri interi positivi corrispondenti ai valori di T (n). Ci concentriamo su due tipi di relazioni di ricorrenza lineari: quelle di ordine costante e quelle di ordine non costante.
- Una relazione di ricorrenza lineare, di ordine costante{ k, a coefficienti costanti e omogenea ha forma: xn = a 1 · xn− 1 + a 2 · xn− 2 +... + ak · xn−k per n ≥ k xi = di per 0 ≤ i ≤ k − 1
3.3 Calcolo della complessit`a di algoritmi ricorsivi 13
cioe l’n-esima incognita xne espressa come combinazione lineare (senza termine noto) delle k incognite che la precedono, a meno che non sia uno dei primi k elementi della successione. Ci sono due casi:
- Se k = 1, la soluzione in forma chiusa `e: xn =
d 0 = O(1) se a 1 = 1 d 0 · a 1 n^ = O(a 1 n) se a 1 > 1
- Se k > 1, la relazione pu`o essere risolta nel seguente modo. Assumiamo che esistano delle soluzioni in forma chiusa del tipo xn = c · zn^ con c 6 = 0 6 = z. Allora sostituendole nella prima equazione si ha c · zn−k^ · (zk^ −
∑k i=1 ai^ ·^ z k−i) = 0 e quindi zk (^) − ∑k i=1 ai^ ·^ z k−i (^) = 0 essendo c 6 = 0 6 = z. Se le radici z 1 , z 2 ,... , zk del polinomio associato alla relazione sono tutte distinte, per la linearita e l’omogeneita della relazione la soluzione in forma chiusa piu generalee: xn =
∑k j=
cj · zj n^ = O(max{zj n^ | 1 ≤ j ≤ k})
dove i cj vengono ricavati per sostituzione nelle ultime k equazioni della relazione.
- Una relazione di ricorrenza lineare, di ordine costante{ k, a coefficienti costanti e non omogenea ha forma: xn = a 1 · xn− 1 + a 2 · xn− 2 +... + ak · xn−k + h per n ≥ k xi = di per 0 ≤ i ≤ k − 1 Se
∑k j=1 aj^6 = 1 ci si riconduce al caso precedente ponendo^ yn^ =^ xn^ +h/(
∑k j=1 aj^ −1), da cui segue che: xn = O(yn)
- Una relazione di ricorrenza lineare, di ordine non costante (cio
e in cui l’n-esima incognita xn none espressa come combinazione a passo costante delle incognite che la precedono) e con lavoro di combinazione: - costante, ha forma: (^) { xn = a · xn/b + c per n > 1 x 1 = d e soluzione: xn =
O(logb n) se a = 1 O(nlogb^ a) se a > 1
- lineare, ha forma: (^) { xn = a · xn/b + (c 1 · n + c 2 ) per n > 1 x 1 = d e soluzione:
xn =
O(n) se a < b O(n · logb n) se a = b O(nlogb^ a) se a > b
- polinomiale, ha forma:{ xn = a · xn/b + (cp · np^ + cp− 1 · np−^1 +... + c 1 · n + c 0 ) per n > 1 x 1 = d e soluzione: xn = O(nlogb^ a) se a > bp
- Qualora il tempo d’esecuzione di un algoritmo ricorsivo sia definito tramite una relazione di ricorrenza di tipo diverso da quelli trattati sopra, la soluzione in forma chiusa pu`o essere ottenuta mediante il metodo delle sostituzioni. Esso consiste nell’ipotizzare una soluzione per la relazione di ricorrenza e nel verificarla per induzione sulla dimensione n dei dati di ingresso andando a sostituirla alle incognite.
3.3 Calcolo della complessit`a di algoritmi ricorsivi 15
ms.max = (ms1.max >= ms2.max)? ms1.max: ms2.max; ms.submax = (ms1.max >= ms2.max)? ((ms2.max >= ms1.submax)? ms2.max: ms1.submax): ((ms1.max >= ms2.submax)? ms1.max: ms2.submax); } return(ms); }
ha tempo d’esecuzione definito da:{ T (n) = 1 + T (n/2) + 1 + T (n/2) + 1 + 1 + 1 = 2 · T (n/2) + 5 per n > 2 T (2) = 1 + 1 + 1 = 3 Questa e una relazione di ricorrenza lineare, di ordine non costante e con lavoro di combinazione costante, la cui soluzionee: T (n) = O(nlog^2 2 ) = O(n)
- Il seguente algoritmo ricorsivo per calcolare il fattoriale di n ≥ 1:
int calcola_fatt_ric(int n) { int fatt;
if (n == 1) fatt = 1; else fatt = n * calcola_fatt_ric(n - 1); return(fatt); }
ha tempo d’esecuzione definito da:{ T (n) = 1 + T (n − 1) + 1 = T (n − 1) + 2 per n ≥ 2 T (1) = 1 + 1 = 2 Questa e una relazione di ricorrenza lineare, di ordine costante 1, a coefficienti costanti e non omogenea. Poich´e la somma dei coefficienti nel lato destro della prima equazionee 1, non si puo passare alla corrispondente relazione omogenea, quindi bisogna applicare il metodo delle sostituzioni. Proviamo che T (n) = 2 · n procedendo per induzione su n ≥ 1: ∗ Sia n = 1. Risulta T (1) = 2 e 2 · 1 = 2, da cui l’assertoe vero per n = 1. ∗ Supponiamo T (n) = 2 · n per un certo n ≥ 1. Risulta T (n + 1) = T (n) + 2 = 2 · n + 2 per ipotesi induttiva. Poich´e 2 · n + 2 = 2 · (n + 1), l’asserto `e vero per n + 1. Di conseguenza: T (n) = O(n)
- In generale, gli algoritmi ricorsivi il cui tempo d’esecuzione
e espresso tramite una relazione di ricorrenza lineare di ordine non costante hanno una complessita asintotica polinomiale, mentre quelli il cui tempo d’esecuzione e espresso tramite una relazione di ricorrenza lineare di ordine costante maggiore di 1 hanno una complessita asintotica esponenziale. Il motivo per cui questi ultimi sono meno efficienti e dato dal modo in cui questi algoritmi operano sui loro dati. Poich´e tali algoritmi non dividono i loro dati in sottoinsiemi disgiunti, finiscono per ripetere gli stessi calcoli piu volte.
16 Complessit`a degli algoritmi
- Esempio: l’esecuzione di calcola fib ric(4) invoca calcola fib ric(3) e calcola fib ric(2), con calcola fib ric(3) che invoca di nuovo a sua volta calcola fib ric(2).
- A parit
a di complessita temporale, un algoritmo ricorsivo e un algoritmo non ricorsivo che risolvono lo stesso problema possono avere complessita spaziali diverse. In particolare, va ricordato che per la sua implementazione un algoritmo ricorsivo necessita dell’allocazione e della manipolazione di una pila per far sı che le varie chiamate ricorsive non interferiscano tra loro, cos`ı da evitare perdite d’informazione e quindi la produzione di risultati errati. flf 4