Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Algoritmi e Strutture Dati: Dispensa del Corso, Appunti di Algoritmi E Strutture Di Dati

Primo e Secondo modulo di Algoritmi e strutture dati

Tipologia: Appunti

2018/2019
In offerta
30 Punti
Discount

Offerta a tempo limitato


Caricato il 05/11/2019

andrea-chiellino
andrea-chiellino 🇮🇹

4.8

(4)

1 documento

1 / 92

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Dispense del Corso di
Algoritmi e Strutture Dati
Marco Bernardo Edoardo Bont`a
Universit`a degli Studi di Urbino “Carlo Bo”
Facolt`a di Scienze e Tecnologie
Corso di Laurea in Informatica Applicata
Versione del 30/11/2007
Queste dispense non sono in nessun modo sostitutive dei testi consigliati.
Queste dispense sono state preparate con L
A
T
E
X.
c
°2007
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56
pf57
pf58
pf59
pf5a
pf5b
pf5c
Discount

In offerta

Anteprima parziale del testo

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 Proprieta 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 puo 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 piu 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 complessita 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 piu 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 quantita di memoria di cui esse necessitanoe determinabile a priori (array).
    • Strutture dati dinamiche: la quantita 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 trattabilita, cioe alla possibilit`a di risolverli in maniera efficiente.
  • Al fine di confrontare i problemi decidibili in modo equo rispetto alla loro trattabilita, 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 riducibilita 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 riducibilita 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 soddisfacibilita: 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. Puo essere il numero di dati di ingresso oppure la quantita di memoria necessaria per contenere tali dati.
  • Al fine di catturare l’intrinseca complessita 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): complessita 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): complessita 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 puo 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 (cioe 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 parita 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