Riassunto Teoria Algoritmi e Strutture Dati
Riassunto Teoria Algoritmi e Strutture Dati
Riassunto Teoria Algoritmi e Strutture Dati
Gli ordini
- Metodo dell’iterazione
Il metodo più intuitivo per risolvere una relazione di ricorrenza consiste nello “srotolare” la
ricorsione riducendola ad una sommatoria dipendente solo dalla dimensione n del problema
iniziale.
Esempio:
n
c+T se n > 1
T k = 2
1 se n = 1
Avremo:
n
T n =c+T
2
n n
T =c+T
2 4
n n
T =c+T
4 8
Quindi otteniamo:
n n n n
T n =c+T = 2c + T = 3c + T =⋯=i∙c+T `
2 4 8 2
9
Per raggiungere il costo base bisogna iterare finché non diventa a = 1 ovvero per:
=
n
= 1 → n = 2` → i = log = n volte
2`
Prendendo i = log = n si può concludere che:
n
T n = log = n ∙ c + T bcd 9 = c ∙ log n + T 1 = O log n
2 e
- Cambiamento di variabili
T n =T n +O 1
Questo non ricade nelle ipotesi del lemma, ma se prendo: n = 2t
u
Si ha che: T 2t = T 2 e +O 1
x
Quindi posso sostituire: R x =R +O 1
2
Utilizzando il lemma si trova che:
a = 0 b = 0 → R x = O log x
Risostituendolo: T n = T 2t = O log x
Da cui: T n = O log log n
Algoritmi di ordinamento
-
Selection Sort (in loco)
1) Si cerca il più piccolo elemento della sottosequenza A[i…n].
2) Si scambia questo elemento con l’elemento i-esimo.
Utile quando si hanno array con chiavi molto piccole e record molto grandi perché ogni elemento
viene spostato una sola volta.
Costi:
Caso peggiore, migliore, medio: O n=
Algoritmo:
void selection(int n, int v[]) { // v = vettore da ordinare; n = lunghezza vettore
int i,j,min;
scambia(v[min],v[i]);
}
}
Algoritmo:
void insertion_sort(int n , int v[]) {
int i, j, value;
Algoritmo:
void BubbleSort(int n , int v[]) {
int i,alto;
for (alto = n – 1; alto > 0; alto-- )
for (i=0; i<alto; i++) {
if (v[i] > v[i+1]) // sostituire > con < per avere un ordinamento decrescente
scambia(v[i+1],v[i]);
}
}
Algoritmo:
void mergesort(int array[], int i, int f) { // i = indice inizio; f = indice fine; c = indice centro
if (i<f) {
int c = (i+f)/2;
mergesort(array, i, c);
mergesort(array, c+1, f);
merge(array, i, c, f);
}
}
Algoritmo:
void quick_sort ( int Vett[] , int min , int Max ) {
int m = min, M = Max, temp, pivot = Vett[(min + Max) / 2];
while (m <= M) {
while (Vett[m] < pivot)
m++;
while (Vett[M] > pivot)
M--;
if (m <= M) {
scambia(Vett[m], Vett[M]);
m++;
M--;
}
};
if (min < M)
QS (Vett, min, M);
if (m < Max)
QS (Vett, m, Max);
}
Algoritmo:
a = vettore di interi da ordinare
c = vettore intermedio
b = vettore finale in cui inserirò gli elementi ordinati
for( i=0; i<k; i++ ) c[i]=0; // azzero vettore intermedio
for( i=0; i<n; i++ ) c[ a[i] ]++; // conto le occorrenze dei valori di a
for( i=1; i<k; i++ ) c[j] = c[j–1] + c[j];
- Radix Sort
Esegue gli ordinamenti per posizione della cifra ma partendo dalla cifra meno significativa.
Questo affinché l’algoritmo non si trovi a dover operare ricorsivamente su sottoproblemi
non valutabili a priori.
Costi:
N: lunghezza dell′array
Caso peggiore: O kN
k: media del numero di cifre degli n numeri
Algoritmo:
d = numero di cifre del valore massimo (se ho: 1,20,400 allora d=3 e i numeri verranno
considerati così: 001, 020, 400).
Si ordinano i valori partendo dalla cifra di destra.
for( i=0; i<d; i++ ) // i = 0 indica la cifra più a destra del valore
“uso un algoritmo stabile (in genere il Bucket Sort) per ordinare l’array rispetto alla i-esima cifra”
- Bucket Sort
1) L’intervallo dei valori, noto a priori, è diviso nel bucket a cui appartiene.
2) Ciascun valore dell’array viene inserito nel bucket a cui appartiene.
3) I valori all’interno dei bucket vengono ordinati.
4) Vengono concatenati i valori contenuti nei bucket.
Caso peggiore: O n log n
Caso migliore: O n se gli elementi nei bucket sono uniformemente distribuiti.
Strutture dati
- Lista
Il nome della lista corrisponde a un indirizzo di memoria che contiene l’indice al primo elemento.
Ogni elemento contiene anche l’indice all’elemento successivo.
L’inserzione in testa costa O 1 , poiché sono all’inizio.
L’inserzione in una posizione i costa O i , perché devo trovarla.
Stessa cosa per la cancellazione.
- Pila
Realizzabile con una lista in cui:
- Push: inserisce in testa alla lista.
- Pop: elimina l’elemento in testa alla lista.
- Top: legge l’elemento in testa.
Tutte costo O 1
Segue la politica LIFO (last input first output): l’ultimo inserito è il primo ad essere tolto.
Posso farla anche con un array (inserisco gli elementi a partire dalla prima posizione dell’array;
quando inserisco devo controllare che ci si spazio nell’array; quando tolgo devo controllare che ci
sia qualcosa da togliere; avrò bisogno di un N che indica quanti elementi ho inserito nell’array).
- Coda
Realizzabile con una lista in cui:
- enqueue: inserire in coda.
- dequeue: rimuove un elemento dalla testa della coda.
Tutte costo O 1
Segue la politica FIFO (first input first output): il primo ad essere inserito è il primo ad essere tolto .
- Albero binario perfettamente bilanciato: è un albero i cui nodi interni hanno esattamente 2 figli e
tutte le fogli sono allo stesso livello.
k=3
k-1 = 2
k=3
- Heap
Proprietà di Struttura: il MaxHeap è un albero binario quasi perfettamente bilanciato con i nodi
all’ultimo livello addossati a sinistra.
Questa proprietà permette di inserire un albero binario in un array.
Proprietà di Valore: in ogni sottoalbero di un MaxHeap, la radice contiene un valore maggiore di
entrambi i valori dei figli (minore nel caso si parli di MinHeap).
Nel MaxHeap, dalla proprietà di valore, segue che nella radice si trova il valore massimo:
20
17 13
5 15 9 11
3 2 4 10 1 8
Posso memorizzarlo in un array leggendolo ad ogni livello, da sinistra a destra:
20 17 13 5 15 9 11 3 2 4 10 1 8
Proprietà 1):
` @
Il nodo in posizione i con i > 1 ha per padre il nodo in posizione (es. = 1).
= =
Gli indici i partono da 1 e non da 0 come negli array; i=1 indica il nodo in array[0] .
Per il motivo indicato sopra, nell’array contenente l’heap si parte ad inserire dalla posizione 1.
Proprietà 2):
Il nodo in posizione i se ha figli, li ha in posizione 2 ∗ i e 2 ∗ i + 1 (figlio sinistro e figlio
destro).
Proprietà 3):
Dato un heap di n nodi:
n
#nodi interni = nodi che hanno almeno un figlio
2
n
#foglie =
2
Altezza albero: h = θ log n
Codice:
// h.v = vettore dell’heap; h.n = elementi contenuti nel vettore (non la lunghezza del vettore)
fixup( int k, heap& h ) {
while( k>1 && h.v[k/2]<h.v[k] ) {
scambia( k, k/2, h );
k = k/2;
}
}
- Se la applico ad un elemento appena inserito (in fondo al vettore), essa sistema tale elemento
nell’MaxHeap (se si vuole convertire un vettore in un MaxHeap, si inserisce un elemento alla volta,
applicando questa funzione).
- Alberi:
- Alberi ordinali e binari:
- Alberi binari di ricerca (anche bilanciati):
- Programmazione dinamica:
Ricerche:
- Visita posticipata: è una visita dell’albero in cui prima si visita il sottoalbero sinistro, poi il destro
e infine la radice.
Ricerca sequenziale:
Ricerca proporzionale:
Ricerca a salti:
Ordinamenti:
- Stabile: un algoritmo di ordinamento è stabile se dato un array in cui ci sono più chiavi uguali,
dopo aver applicato l’algoritmo, esse mantengono lo stesso ordinamento relativo.
Merge Sort: è stabile.