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: dispense, Sintesi del corso di Algoritmi E Strutture Di Dati

Dispense riguardanti Algoritmi e strutture dati sui seguenti argomenti: - Analisi di algoritmi - Ricerca in una lista - Algoritmi di ordinamento caratteristiche - Selection Sort - Insertion Sort - Bubble Sort - Tecnica di Divide et impera - Merge Sort - Quick Sort - Union-find

Tipologia: Sintesi del corso

2019/2020

In vendita dal 09/07/2020

mauro-tellaroli
mauro-tellaroli 🇮🇹

2.7

(3)

9 documenti

1 / 18

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Algoritmi e Strutture dati
Mauro Tellaroli
Indice
1 Analisi di algoritmi 2
1.1 Criteri di costo uniforme e logaritmico . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Notazioneasintotica .................................. 2
1.3 Costo di algoritmi e complessit`a di problemi . . . . . . . . . . . . . . . . . . . . . 2
1.4 Caso peggiore, caso migliore e caso medio . . . . . . . . . . . . . . . . . . . . . . 3
2 Ricerca in una lista 3
2.1 Ricercasequenziale................................... 4
2.1.1 Analisi della ricerca sequenziale . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Ricerca binaria (o dicotomica) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2.1 Analisi ricerca binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Algoritmi di ordinamento 6
3.1 Stabilit`a......................................... 6
3.2 Ordinamento interno e esterno . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 Algoritmoinloco.................................... 7
4 Selection Sort 7
4.1 Analisitempo...................................... 8
4.2 Analisispazio...................................... 8
5 Insertion Sort 8
5.1 Analisitempo...................................... 9
5.2 Analisispazio...................................... 9
6 Bubble sort 9
6.1 Analisitempo...................................... 10
6.2 Analisispazio...................................... 10
7 Divide et impera 11
7.1 Mastertheorem..................................... 11
8 Merge sort 12
8.1 Notesullopseudocodice6 ............................... 13
8.2 Analisitempo...................................... 13
8.3 Analisispazio...................................... 13
1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12

Anteprima parziale del testo

Scarica Algoritmi e strutture dati: dispense e più Sintesi del corso in PDF di Algoritmi E Strutture Di Dati solo su Docsity!

Algoritmi e Strutture dati

Mauro Tellaroli

  • 1 Analisi di algoritmi Indice
    • 1.1 Criteri di costo uniforme e logaritmico
    • 1.2 Notazione asintotica
    • 1.3 Costo di algoritmi e complessit`a di problemi
    • 1.4 Caso peggiore, caso migliore e caso medio
  • 2 Ricerca in una lista
    • 2.1 Ricerca sequenziale
      • 2.1.1 Analisi della ricerca sequenziale
    • 2.2 Ricerca binaria (o dicotomica)
      • 2.2.1 Analisi ricerca binaria
  • 3 Algoritmi di ordinamento
    • 3.1 Stabilit`a
    • 3.2 Ordinamento interno e esterno
    • 3.3 Algoritmo in loco
  • 4 Selection Sort
    • 4.1 Analisi tempo
    • 4.2 Analisi spazio
  • 5 Insertion Sort
    • 5.1 Analisi tempo
    • 5.2 Analisi spazio
  • 6 Bubble sort
    • 6.1 Analisi tempo
    • 6.2 Analisi spazio
  • 7 Divide et impera
    • 7.1 Master theorem
  • 8 Merge sort
    • 8.1 Note sullo pseudocodice
    • 8.2 Analisi tempo
    • 8.3 Analisi spazio

9 Quick sort 14 9.1 Note sullo pseudocodice 7............................... 14 9.2 Analisi tempo...................................... 15 9.3 Analisi spazio...................................... 16 9.4 Note sullo pseudocodice 10.............................. 17

10 Union-find 17 10.1 QuickFind........................................ 17 10.1.1 Analisi Tempo................................. 17 10.2 QuickUnion....................................... 18 10.2.1 Analisi Tempo................................. 18 10.3 QuickFind con bilanciamento sulle union....................... 18 10.3.1 Analisi Tempo................................. 18 10.4 QuickUnion bilancito in altezza............................ 18 10.4.1 Analisi Tempo................................. 18

1 Analisi di algoritmi

1.1 Criteri di costo uniforme e logaritmico

Utilizzando il criterio di costo uniforme ciascuna operazione viene contata come singolo passo, indipendentemente dalla dimensione degli operandi coinvolti nell’operazione stessa. Sebbene sia comodo, si tratta di un modello troppo idealizzato: esso assume infatti che registri e memoria siano costruiti da parole di grandezza arbitrariamente grande. A risolvere questo problema `e il criterio di costo logaritmico che assume che il costo di esecuzione delle istruzioni dipenda dalla dimensione degli operandi coinvolti. Quindi eseguire un’operazione su un numero n implica pagare un costo proporzionale a log n.

1.2 Notazione asintotica

Data una funzione f (n) definiamo:

  • g(n) = O(f (n)) ⇐⇒ ∃c > 0 , n 0 > 0 : g(n) ≤ cf (n) ∀n ≥ n 0 ;
  • g(n) = Ω(f (n)) ⇐⇒ ∃c > 0 , n 0 > 0 : g(n) ≥ cf (n) ∀n ≥ n 0 ;
  • g(n) = Θ(f (n)) ⇐⇒ ∃c 1 , c 2 > 0 , n 0 > 0 : c 1 f (n) ≤ g(n) ≤ c 2 f (n) ∀n ≥ n 0.

Pi`u intuitivamente, per n sufficientemente grande e per opportune costanti c, c 1 , c 2 si ha:

  • se g(n) = O(f (n)), allora g(n) cresce al pi`u come f (n);
  • se g(n) = Ω(f (n)), allora g(n) cresce almeno come f (n);
  • se g(n) = Θ(f (n)), allora g(n) cresce esattamente come f (n).

1.3 Costo di algoritmi e complessit`a di problemi

Si puo esprimere il costo di un algoritmo A e la complessita di un problema P attraverso la notazione asintotica:

  • A ha costo O(f (n)) se ogni istanza in ingresso di dimensione n viene risolta in al pi`u f (n) passi;

2.1 Ricerca sequenziale

L’algoritmo controlla in sequenza gli elementi dell’insieme, arrestandosi quando trova l’elemento cercato; non potendosi avvalere di alcun ordinamento tra gli elementi, l’algoritmo pu`o concludere con certezza che l’insieme non contiene alcun elemento corrispondente solo dopo averli verificati tutti.

Pseudocodice 1: Ricerca sequenziale 1 Algoritmo ricercaSequenziale (array A[0...n − 1], elemento x) → booleano 2 i ← 0 3 while i < n and A[i] 6 = x do 4 i ← i + 1 5 if i = n then return False 6 else return True

2.1.1 Analisi della ricerca sequenziale

Caso migliore. L’elemento `e in prima posizione; la ricerca esegue solo un confronto:

Tbest(n) = 1

Caso peggiore. L’elemento `e nell’ultima posizione o non appartiene alla lista; la ricerca controlla tutti gli elementi: Tworst(n) = n

Caso medio. Come visto nel paragrafo 1.4 per calcolare il caso medio abbiamo bisogno della probabilita del verificarsi di un istanza. Un’ipotesi sensatae: se x e nella lista, puo occupare qualsiasi posizione (pos) con la stessa probabilita, e quindi per ogni possibile posizione la probabilita e (^) n^1 ; inoltre se xe in posizione i verr`a trovato dopo i confronti, quindi: { ∀i ∈ [1, n] P(pos(x) = i) = (^) n^1 ∀i T (i) = i

Tavg (n) =

∑^ n

i=

(P(pos(x) = i) · i) =

∑^ n

i=

n

· i) =

n

∑^ n

i=

i =

n

n(n + 1) 2

n + 1 2

2.2 Ricerca binaria (o dicotomica)

La ricerca binaria richiede un accesso diretto agli elementi della lista in base alla posizione ma, soprattutto, richiede una lista gi`a ordinata. Prendiamo un elemento m in posizione centrale dell’array A e confrontiamolo con l’elemento x da noi cercato: se m < x, restringiamo la ricerca alle posizioni successice a m; altrimenti restringiamo a quelle precedenti.

Pseudocodice 2: Ricerca binaria (o dicotomica) 1 Algoritmo ricercaBinaria (array A[0...n − 1], elemento x) → indice 2 sx ← 0 3 dx ← n 4 pos ← − 1 5 while sx < dx and pos = − 1 do 6 m ← (sx + dx)/ 2 7 if x = A[m] then 8 pos ← m 9 else if x < A[m] then 10 dx ← m 11 else 12 sx ← m + 1

13 return pos

2.2.1 Analisi ricerca binaria

Caso migliore. Si verifica quando x `e nella posizione centrale di A (x = A[n/2]):

Tbest(n) = 1

Caso peggiore. Si verifica quando sx = dx, ovvero quando la dimensione del sottoarray esaminato e 1. Per semplicita supponiamo che n sia una potenza di 2. Ad ogni passo il sottoarray e lungo la meta dell’array di partenza. Quindi il primo sottoarray e lungo n 2 , il secondo 2 n 2 , il terzo 2 n 3 ,..., fino all’ultimo che saro lungo 2 ni , dove i e il numero di passi effettuati. Ma come detto all’inzio sappiamo che quest’ultimoe lungo 1 quindi:

n 2 i^

= 1 ⇔ n = 2i^ ⇔ i = log 2 n

Tworst(n) = O(log n)

Caso medio. Supponiamo che n sia una potenza di 2 e che x possa occupare qualsiasi posizione di A con la stessa probabilita ( (^1) n ). L’algoritmo potra poi eseguire al minimo 1 confronto e al massimo dlog 2 ne confronti (1 ≤ i ≤ log n). Analizziamo ora quante volte verranno fatti i confronti in base alla posizione dell’elemento x:

  • 1 confronto accadra solo per 1 posizione ( n 2 ); la probabilita `e (^) n^1 ;
  • 2 confronti accadra per 2 posizioni ( n 4 , 34 n ); la probabilita `e 2 · (^) n^1 ;
  • 3 confronti accadra per 2^2 posizioni ( n 8 , 38 n , 58 n , 78 n ); la probabilita `e 2^2 · (^1) n ; -...
  • i confronti accadra per 2i−^1 posizioni; la probabilita `e 2

i− 1 n.

Quindi nel caso medio il numero di confronti sar`a:

Tavg (n) =

log∑ n

i=

#conf ronti · P(ci siano # confronti) =

3.3 Algoritmo in loco

Un algoritmo si dice in loco quando `e in grado di trasformare una struttura dati utilizzando soltanto un piccolo e costante spazio di memoria extra. I dati in ingresso sono solitamente sovrascritti con il risultato prodotto durante l’esecuzione dell’algoritmo.

4 Selection Sort

Il principio dell’algoritmo e dividere l’array (lungo n) in 2 parti: una parte A ordinata e una parte B da ordinare. All’inizio B sara lunga n mentre A sara lunga 0. Man mano che si andra avanti si selezionera un elemento in B che andra poi definitivamente messo in A. In questo modo si toglie un elemento da B e lo si mette in A. Alla fine si avra A lunga n e quindi l’array sara tutto ordinato. Una maniera per eseguire l’algoritmo e scegliere l’elemento piu piccolo in B. L’algoritmo e chiamato ”Selection sort” in quanto ad ogni iterazione viene selezionato un elemento che andra poi al suo posto definitivo.

Figura 1: Esempio di esecuzione del Selection sort. Si noti la parte celeste che denota A e la parte rosa che denota B

Pseudocodice 3: Selection Sort 1 Algoritmo selectionSort (array A[0...n − 1]) 2 for i ← 0 to n − 2 do 3 m ← i 4 for k ← i + 1 to n − 1 do 5 if A[k] < A[m] then 6 m ← k

7 scambia A[m] con A[i]

4.1 Analisi tempo

L’unica operazione costosa dell’algoritmo `e il confronto tra 2 elementi. Per questo stimare il numero di confronti eseguiti (C(n)) equivale a stimare il tempo (T (n)). Analizziamo il numero di confronti (Ci) ad ogni iterazione:

  • Nella ultima iterazione Cn = 1;
  • Nella penultima iterazione Cn− 1 = 2;
  • Nella terzultima iterazione Cn− 2 = 3; -...
  • Nella prima iterazione C 1 = n − 1

Quindi:

C(n) = 1 + 2 + 3 + ... + n − 1 =

n∑− 1

i=

i =

n(n − 1) 2

= Θ(n^2 )

Da notare il fatto che nel selection sort il numero di confronti effettuati `e indipendente dal contenuto dell’array. Vengono sempre eseguiti n(n 2 − 1)confronti:

Tbest(n) = Tworst(n) = Tavg (n) =

n(n − 1) 2

4.2 Analisi spazio

Siccome l’algoritmo e in loco e non viene utilizzata la ricorsione, lo spazioe costante:

S(n) = O(1)

5 Insertion Sort

Anche qui, come nel selection sort si divide l’array in 2 parti: una ordinata A e una non ordinata B. Si parte con A lunga 1 e B lunga n − 1. Ad ogni passo si toglie un elemento da B e lo si inserisce nella giusta e definitiva posizione in A. L’algoritmo `e chiamato ”Insertion sort” in quanto ad ogni iterazione un elemento viene inserito al suo posto nel sottoarray ordinato.

elementi piu grandi si accumulano sul fondo l’ultima parte dell’array sara gia ordinata e non dovra piu essere ricontrollata. L’algoritmoe chiamato ”Bubble sort” siccome le ”bolle” (gli elementi) pi`u grosse vanno in cima all’array.

Pseudocodice 5: Bubble Sort 1 Algoritmo bubbleSort (array A[0...n − 1]) 2 j ← 1 3 do 4 scambio ← f alse 5 for i ← 1 to n − j do 6 if A[j − 1] > A[j] then 7 scambia A[j] con A[j − 1] 8 scambio ← true

9 j ← j + 1 10 while scambio and j < n

6.1 Analisi tempo

Caso migliore. Il caso migliore si verifica con un array gi`a ordinato; in questo caso nel ciclo interno (riga 5) non viene effettuato nessuno scambio e quindi il ciclo principale (riga 3) termina:

Tbest(n) = n − 1

Caso peggiore. Il caso peggiore si verifica con un array ordinato al contrario. Consideriamo le iterazioni del ciclo principale (riga 3); ad ogni iterazione il ciclo interno (riga 5) esegue n − j confronti:

  • Alla prima iterazione j = 1 quindi C 1 (n) = n − j = n − 1
  • Alla seconda iterazione j = 2 quindi C 2 (n) = n − 2 -...
  • Alla j−esima iterazione Cj (n) = n − j

Quindi siccome il ciclo principale viene eseguito n − 1 volte:

Tworst(n) =

n∑− 1

j=

Cj (n) =

n∑− 1

j=

j =

n(n − 1) 2 = O(n^2 )

6.2 Analisi spazio

Siccome l’algoritmo e in loco e non viene utilizzata la ricorsione lo spazioe costante:

S(n) = O(1)

7 Divide et impera

La tecnica divide-et-impera e una tecnica di progettazione di algoritmi il cui principio consiste nel dividere il generico input di dimensione n in un certo numero m di sottoistanze del medesimo problema, ciascuna di dimensione na (circa) per qualche a > 1; quindi richiama ricorsivamente se stesso su tali istanze ridotte e poi ricompone i risultati parziali ottenuti attraverso una funzione di tempo g(n), ottenendo cosı la soluzione del problema principale. Il tempo di calcolo di algoritmi divide-et-impera `e soluzione di un equazione di ricorrenza della seguente forma:

T (n) = mT

( (^) n a

  • g(n)

7.1 Master theorem

Teorema. La relazione di ricorrenza

T (n) =

mT ( na ) + nc^ se n > 1 b se n = 1

⇒ T (n) =

Θ(nc) se m < ac Θ(nc^ log n) se m = ac Θ(nloga^ m) se m > ac

8.1 Note sullo pseudocodice 6

  • Riga 2-3. Per ottimizzare lo spazio viene usato sempre lo stesso array ausiliario X che viene passato come argomento alla funzione.
  • Riga 5. La procedura mergeSort ordina l’array A[i...f − 1] utilizzando come array ausiliario X.
  • Riga 6. I passi ricorsivi vengono eseguiti solo se la lunghezza del sottoarray `e maggiore di
  • Riga 12. La procedura merge esegue il merge tra A[i...m − 1] e A[m...f − 1] utilizzando come array ausiliario X.

8.2 Analisi tempo

Utilizziamo il Master theorem (Paragrafo 7.1) per analizzare il tempo dell’algoritmo. Il problema viene diviso in 2 sottoistanze grandi n 2 (assumendo n pari); ricomporre poi i risultati (merge) costa al pi`u n − 1 confronti. Otteniamo quindi un’equazione di ricorrenza:

T (n) = 2T

( (^) n 2

  • n − 1 = 2T

( (^) n 2

  • Θ(n)

Θ(n) pu`o essere scritto come n quindi:

T (n) = 2T

( (^) n 2

  • n^1

Applicando il master theorem otteniamo:

T (n) = Θ(n log n)

8.3 Analisi spazio

Possiamo anche qui considerare la struttura ricorsiva dell’algoritmo e usare il master theorem per stimare S(n). Ad ogni chiamata ricorsiva corrisponde un record di attivazione che ha peso costante (1). Inoltre la chiamata ricorsiva pu`o essere analizzata ricorsivamente come segue:

  • Se n ≤ 1 allora viene utilizzato solo il record di attivazione corrente: S(n) = 1
  • Se n > 1 allora vengono effettuate 2 chiamate ricorsive, entrambe su un array di dimensione uguale supponendo n potenza di 2. Siccome al termine di una chiamata ricorsiva lo spazio utilizzato nello stack viene ”ripulito”, l’altra chiamata usa lo stesso spazio della prima.

Dunque:

S(n) =

S

( (^) n 2

  • 1 n > 1 1 altrimenti

Possiamo vedere 1 come n^0 e quindi:

S(n) =

S

( (^) n 2

  • n^0 n > 1 1 altrimenti

Dal master theorem concludiamo che:

S(n) = Θ(n^0 log n) = Θ(log n)

9 Quick sort

Anche questo algoritmo, come il mergeSort, si basa sulla tecnica divide-et-impera. Se nel mergeSort la parte di divide e semplice e la parte di imperae piu complicata, nel quickSort si invertono le cose. Il principio di funzionamentoe il seguente:

  • Divide. Scegli un elemento x detto perno, partiziona l’array in due sottoarray contenenti rispettivamente tutti gli elementi ≤ x e > x, ed ordina rispettivamente le due sottosequenze.
  • Impera. Concatena le due sottosequenze ordinate.

Supponiamo di dover partizionare il seguente array:

28 36 65 3 2 11 60 74 Scegliamo come perno (o pivot) 36 e costruiamo le due sottosequenze:

28 3 2 11 36 65 60 74 Si noti che la posizione del perno dopo la partizione rimarra definitiva. Quindi l’algoritmo puo successivamente riordinare la parte di array sinistra del perno e quella destra.

Pseudocodice 7: Partizionamento del quickSort 1 Algoritmo partiziona (array A, indice i, indice f ) → indice 2 perno ← A[i] 3 sx ← i 4 dx ← f 5 while sx < dx do 6 do 7 dx ← dx − 1 8 while A[dx] > perno 9 do 10 sx ← sx + 1 11 while sx < dx and A[sx] ≤ perno 12 if sx < dx then 13 scambia A[sx] con A[dx]

14 scambia A[i] con A[dx] 15 return dx

9.1 Note sullo pseudocodice 7

  • Riga 1. L’algoritmo partiziona l’array A[i...f ];
  • Riga 15. L’algoritmo restituisce l’indice del perno in posizione finale.

Cb(n) = Θ(n log n)

Caso medio. Assumiamo una distribuzione uniforme e quindi la probabilita che il perno si trovi in posizione ke (^) n^1. Applicando poi la definizione di tempo medio:

C(n) =

n

n∑− 1

k=

(C(k) + C(n − k − 1) + n) =

= n +

n

n∑− 1

k=

C(k)

Si pu`o, dopo una serie di passaggi abbastanza complessi, concludere che:

C(n) ≤ 1 .39 log 2 n Una domanda puo sorgere spontanea: perche l’algoritmo e detto quickSort (veloce) se Cw(n) = Θ(n^2 ) come negli algoritmi piu semplici? La risposta e che il caso peggioree estremamente raro; si puo notare cio dal fatto che sia il caso medio che il caso migliore sono dell’ordine di n log n. Inoltre la costante trovata nell’analisi del caso medio, ovvero 1.39, e molto bassa. Le prestazione dell’algoritmo sono molto influenzate dalla scelta del perno. Spesso si utilizzano delle strategie casuali, per cercare di evitare il caso peggiore, come la scelta di un elemento casuale come perno o nel riordinare a caso l’array in partenza. L’algoritmo none stabile.

9.3 Analisi spazio

L’algoritmo e in loco ma viene utilizzata la ricorsione. Bisogna tenere quindi conto dello spazio occupato nello stack dalle chiamate ricorsive. Ogni record di attivazione contiene solo i e f quindi la dimensionee costante. Sara quindi il numero massimo di record di attivazione presenti nello stack contemporaneamente a determinarne lo spazio. Il caso peggiore si verifica quando, come nel caso peggiore del tempo, la suddivisonee sempre sbilanciata. Qui verranno memorizzate nello stack n record di attivazione. Una soluzione per ridurre le chiamate ricorsive `e utilizzare un’iterazione come mostrato nello pseudocodice 9.

Pseudocodice 9: QuickSort con iterazione 1 Algoritmo quickSort (array A, indice i, indice f ) 2 while f − 1 > 1 do 3 m ← partiziona(A, i, f ) 4 quickSort(A, i, m) 5 i ← m + 1

In questo modo esegue le chiamate ricorsive solo sulla parte sinistra dell’array. Il problema peroe che se i sottoarray di destra fossero nulli, l’altezza dello stack resterebbe comunque n. La soluzione a cioe eseguire le chiamate ricorsive solo al sottoarray piu corto dei due. Il caso peggiore che possa capitaree trovare ad ogni passo un sottoarray di lunghezza n/2. In questo caso lo spazio potr`a essere definito con:

Pseudocodice 10: QuickSort con riduzione dell’altezza dello stack 1 Algoritmo quickSort (array A, indice i, indice f ) 2 while f − 1 > 1 do 3 m ← partiziona(A, i, f ) 4 if m − i < f − m then 5 quickSort(A, i, m) 6 i ← m + 1 7 else 8 quickSort(A, m + 1, f ) 9 f ← m

S(n) = S

( (^) n 2

Dove +1 indica lo spazio costante occupato da un record di attivazione. Risolvendo l’equazione si ottiene quindi:

S(n) = Θ(log n)

9.4 Note sullo pseudocodice 10

  • Riga 4. Si controllo che il sottoarray sinistro sia piu piccolo; se loe si esegue la chiamata ricorsiva sul sottoarray A[i...m] altrimenti si esegue sul sottoarray A[m + 1...f ]

10 Union-find

Lo scopo di questa struttura dati `e tenere traccia di un insieme di elementi, partizionato in un certo numero di sottoinsiemi disgiunti. Le operazioni fondamentali sono:

  • makeSet(e): crea un insieme contenente unicamente e; l’insieme {e} avr`a nome e.
  • find(e): restituisce il nome dell’insieme contenente l’elemento e.
  • union(A, B): restituisce un insieme contenente gli elementi di A e di B.

10.1 QuickFind

La union-find viene implementata attraverso alberi di altezza 1 che rappresentano gli insiemi. Le foglie dell’albero contengono gli elementi dell’insieme mentre la radice ne contiene il nome.

10.1.1 Analisi Tempo

  • makeSet(e): crea l’albero contenente e: T (n) = O(1).
  • find(e): restituisce il padre del nodo e: T (n) = O(1).
  • union(A, B): per ogni elemento di B cambia la radice con quella di A: T (n) = O(n).