La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Algoritmi e Strutture Dati Laurea in Informatica

Presentazioni simili


Presentazione sul tema: "Algoritmi e Strutture Dati Laurea in Informatica"— Transcript della presentazione:

1 Algoritmi e Strutture Dati Laurea in Informatica
Calendario: 27 Febbraio – 13 Giugno Aula: LuM250 Orario: Mer Gio, Ven Numero crediti = 9 (~ 72 ore) ~ 52 ore di teoria, ~20 ore di esercizi Docenti: Paolo Baldan (5crediti) Michele Scquizzato (4 crediti)

2 Modalità d’Esame Prova intermedia per l’assegnazione di un bonus fino a 3 punti (che si somma al voto dell’esame finale) Prova scritta (5 appelli - indispensabile iscriversi nella lista di esame che verrà attivata su UNIWEB) Registrazione, con possibile orale (su base volontaria). Indispensabile per conseguire la lode.

3 Materiale didattico Testo: Introduzione agli Algoritmi e Strutture Dati (3° ed). T.H.Cormen, C.E.Leiserson, R.L.Rivest, C.Stein. McGraw-Hill. Trad. di: Introduction to Algorithms and Data Structures (3° ed). T.H.Cormen, C.E.Leiserson, R.L.Rivest, C.Stein. MIT Press. Pagina del corso: + Moodle con altro materiale

4 Programma Le prime 5 parti del testo (con qualche omissione):
Fondamenti: notazione per gli algoritmi e primi esempi di algoritmi e di analisi degli algoritmi Ordinamento e statistiche d’ordine Strutture dati fondamentali Tecniche avanzate di progettazione ed analisi degli algoritmi Strutture dati avanzate

5 INTRODUZIONE Esempio1: problema dell’ordinamento
I problemi computazionali, gli algoritmi che li risolvono e le tecniche per sviluppare tali algoritmi Esempio1: problema dell’ordinamento Input: a1,a2,...,an Output: a'1,a'2,...,a'n permutazione (riarrangiamento) di a1,a2,...,an tale che a'1  a'2  ...  a'n

6 TECNICA INCREMENTALE

7 Soluzione1: Algoritmo Insertion-Sort.
j 1 n j A key 1 n j A i Usare questo esempio per illustrare la sintassi della pseudocodifica 1 n j A i key 1 n j A i key 1 n j A 1 n A j

8 // inseriamo A[j] nella sequenza // ordinata A[1..j-1] i = j - 1
Insertion-Sort(A) n = A.length for j = 2 to n key = A[j] // inseriamo A[j] nella sequenza // ordinata A[1..j-1] i = j - 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i – 1 A[i+1] = key Usare questo esempio per illustrare la sintassi della pseudocodifica

9 // inseriamo A[j] nella // sequenza ordinata // A[1..j-1] i = j – 1
Insertion-Sort(A) n = A.length for j = 2 to n key = A[j] // inseriamo A[j] nella // sequenza ordinata // A[1..j-1] i = j – 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i – 1 A[i+1] = key void Insertion-Sort(vector<T> A) { int i, j, n = A.size(); T key; for (j = 1; j < n; j++) key = A[j]; // inseriamo A[j] nella // sequenza ordinata // A[0..j-1] i = j – 1; while (i >= 0 && A[i] > key) A[i+1] = A[i]; i--; } A[i+1] = key; Usare questa slide per evidenziare similarità e differenze con il linguaggio C++.

10 A key Insertion-Sort(A) n = A.length for j = 2 to n // inserisce A[j] nella // sequenza ordinata // A[1..j-1] key = A[j] i = j – 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i – 1 A[i+1] = key 5 # 2 # 2 5 # 8 2 5 # 2 5 8 # 4 2 # # 1 3 6 7 2 4 5 #

11 A key Insertion-Sort(A) n = A.lenght for j = 2 to n // inserisce A[j] nella // sequenza ordinata // A[1..j-1] key = A[j] i = j – 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i – 1 A[i+1] = key # 3 6 1 # # 6 3 1 2 # # 6 # 7 8

12 Correttezza InsertionSort
Analisi di Insertion-Sort: correttezza Insertion-Sort(A) n = A.length for j = 2 to n key = A[j] i = j - 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i – 1 A[i+1] = key 1 n j A Introdurre il concetto di invariante di un ciclo ed illustrare il suo uso nella dimostrazione di correttezza di InsertionSort. 1 n j A i 1 n j A i 1 n j A i 1 n j A Correttezza InsertionSort

13 Analisi di Insertion-Sort: complessità Insertion-Sort(A) //
n = A.length // for j = 2 to n // key = A[j] // i = j // while i > 0 and A[i] > key // A[i+1] = A[i] // i = i – // A[i+1] = key // Tempo richiesto per eseguire l’algoritmo calcolato come tempo richiesto per eseguire ciascuna istruzioni elementare moltiplicato per il numero di volte che l’istruzione viene ripetuta.

14 caso migliore:

15 caso peggiore:

16 caso medio:

17 TECNICA DIVIDE ET IMPERA

18 Soluzione2: Algoritmo Merge-Sort
5 2 8 0 4 7 2 6 1 9 3 2 5 8 5 2 8 0 4 7 0 4 7 1 3 9 1 9 3 2 6 2 6 5 2 8 0 4 7 1 9 3 6 2 5 2 2 5 8 8 0 4 0 4 7 7 1 9 1 9 3 3 2 2 6 6 2 5 4 9 1 5 5 2 2 4 4 1 1 9 9

19 Merge-Sort(A,p,r) if p < r q = (p+r)/2 Merge-Sort(A,p,q) Merge-Sort(A,q+1,r) Merge(A,p,q,r)

20 A[p..q] A[q+1..r] L 7 8  6 9  7 8  9  5 7 8  6 9  9  8  9  0 1 2 6 9  0 1 3 6 9  R A[p..r]

21 Merge(A,p,q,r) n1 = q – p + 1 n2 = r – q for i = 1 to n1 L[i] = A[p + i – 1] for j = 1 to n2 R[j] = A[q + j] L[n1 + 1] = R[n2 + 1] =  i = j = 1 for k = p to r if L[i]  R[j] A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 1

22 Analisi di Merge-Sort: correttezza
Merge-Sort(A,p,r) if p < r // altrimenti A[p..r] è ordinato q = (p+r)/2 Merge-Sort(A,p,q) Merge-Sort(A,q+1,r) Merge(A,p,q,r) non ordinati 1 p r n A ordinati 1 p r n q A ordinati 1 p r n A

23 Merge(A,p,q,r) n1 = q – p + 1 n2 = r – q for i = 1 to n1 L[i] = A[p + i – 1] for j = 1 to n2 R[j] = A[q + j] L[n1 + 1] = R[n2 + 1] =  i = j = 1 for k = p to r if L[i]  R[j] A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 1 1 p r n q A 1 p r n A L R n1 n2 1 p r n A L R n1 n2 k i j 1 p r n A L R n1 n2 k i j

24 Merge(A,p,q,r) // complessità //
n1 = q – p // n2 = r – q // for i = 1 to n1 // L[i] = A[p + i – 1] // for j = 1 to n2 // R[j] = A[q + j] // L[n1 + 1] = R[n2 + 1] =  // i = j = 1 // for k = p to r // if L[i]  R[j] // A[k] = L[i] // i = i // else A[k] = R[j] // j = j //

25 Merge-Sort(A,p,r) //complessità //
if p < r // q = (p+r)/2 // Merge-Sort(A,p,q) // Merge-Sort(A,q+1,r) // Merge(A,p,q,r) //

26

27 Dunque esiste N tale che
per ogni n > N. Qualunque siano i valori delle costanti a', b', c', a", b" e c" l’algoritmo Merge-Sort è superiore a Insertion-Sort per array di dimensione sufficientemente grande.

28 Possiamo dire che “cresce come” n2 mentre “cresce come” n log2 n.
IS n2 ns MS n log2 n ns 10 100 33 0.1s 0.033s 100 10000 664 10s 0.664s 1000 106 9965 1ms 10s 10000 108 132877 0.1s 133s 106 1012 2·107 17m 20ms 109 1018 3·1010 70anni 30s 109 1018 3·1010 30s

29 dunque esiste N tale che
per ogni n > N. Qualunque siano i valori delle costanti a', b', c', a", b" l’algoritmo Insertion-Sort è superiore a Merge-Sort per array (quasi) ordinati e sufficientemente grandi.

30 Insertion-Sort è anche superiore a Merge-Sort per array piccoli in quanto le costanti a', b', c' in sono generalmente molto maggiori delle costanti a", b" e c" in Questo suggerisce una modifica di Merge-Sort in cui le porzioni di array di dimensione minore di una certa costante k si ordinano con Insertion-Sort invece di usare ricorsivamente Merge-Sort.

31 Soluzione3: Algoritmo Merge-Ins-Sort
Merge-Ins-Sort(A,p,r) if p < r if r-p+1 < 32 InsertSort(A,p,r) else q = (p+r)/2 Merge-Ins-Sort(A,p,q) Merge-Ins-Sort(A,q+1,r) Merge(A,p,q,r)

32 Complessità e Notazione Asintotica
Quando si confrontano algoritmi, determinare il tempo di esecuzione È complicato Contiene dettagli inutili che vorremmo ignorare Dipende da costanti non note vogliamo darne una visione più astratta (tasso di crescita)

33 Paragonare tra loro algoritmi
Abbiamo una scala di complessità: vogliamo inserire ogni algoritmo in questa scala

34 Un algoritmo può richiedere tempi diversi per input della stessa taglia. Ad esempio il tempo per ordinare n oggetti può dipendere dal loro ordine iniziale. complessità massima complessità media complessità minima

35 Nell’analizzare la complessità tempo di un algoritmo siamo interessati a come aumenta il tempo al crescere della taglia n dell’input. Siccome per valori “piccoli” di n il tempo richiesto è comunque poco, ci interessa soprattutto il comportamento per valori “grandi” di n (il comportamento asintotico)

36 Inoltre, siccome la velocità del processore influisce sul tempo calcolo per una costante moltiplicativa noi valuteremo la complessità a meno di una tale costante. Questo giustifica le seguenti definizioni:

37 Notazione asintotica O (limite superiore asintotico)
O(g(n))

38 Scriveremo f(n) = O(g(n)) per dire che f(n) è una delle funzioni dell’insieme O(g(n))
si legge: f(n) è “o grande” di g(n) Se f(n) = O(g(n)) rappresenta il tempo calcolo richiesto da un algoritmo diciamo che O(g(n)) è un limite superiore asintotico per la complessità tempo di tale algoritmo.

39 esempi infatti per c = 4 ed n0 = 5
Vedremo che in generale per a2 > 0 a_2n^2+a_1n+a_0 \leq cn^2 a_2+a_1/n+a_0/n^2 \leq c c=2a_2 a_1/n+a_0/n^2 \leq a_2 a_1/n+a_0/n^2 \leq (|a_1|+|a_0|)/n \leq a_2 Vero per n \geq (|a_1|+|a_0|)/a_2 infatti per c = 3 ed n0 = 1

40 Notazione asintotica . (limite inferiore asintotico)

41 Scriveremo f(n) = (g(n)) per dire che f(n) è una delle funzioni dell’insieme (g(n)).
si legge: f(n) è “omega” di g(n) Se f(n) = (g(n)) rappresenta il tempo calcolo richiesto da un algoritmo diciamo che (g(n)) è un limite inferiore asintotico per la complessità tempo di tale algoritmo.

42 esempi infatti per c = 1 ed n0 = 10
Vedremo che in generale se a2 > 0 a_2n^2+a_1n+a_0 \geq cn^2 a_2+a_1/n+a_0/n^2 \geq c c=a_2/2 a_2/2 \geq -a_1/n-a_0/n^2 -a_1/n-a_0/n^2 \leq (|a_1|+|a_0|)/n \leq a_2/2 Vero per n \geq 2(|a_1|+|a_0|)/a_2 infatti per c = 1 ed n0 = 1

43 Notazione asintotica . (limite asintotico stretto)

44 Scriveremo f(n) = (g(n)) per dire che f(n) è una delle funzioni dell’insieme (g(n)).
si legge: f(n) è “theta” di g(n) Se f(n) = (g(n)) rappresenta il tempo calcolo richiesto da un algoritmo diciamo che (g(n)) è un limite asintotico stretto per la complessità tempo di tale algoritmo.

45 esempi Dunque per c1 = 1, c2 = 4 ed n0 = 10 Dunque

46 per ogni n  n0 allora altrimenti per ogni n  n0. Assurdo! per ogni n  n0 allora altrimenti per ogni n  n0. Assurdo!

47 Metodo del limite Spesso è possibile determinare dei limiti asintotici calcolando il limite di un rapporto. Ad esempio se allora per ogni  > 0 esiste n0 tale che per n ≥ n0 Preso 0 <  < k e posto c1 = k   e c2 = k + e quindi

48 Se diciamo che ed in questo caso Se diciamo che ed in questo caso Attenzione: quando il limite del rapporto non esiste questo metodo non si può usare.

49 In generale per ogni funzione polinomiale di grado k con coefficiente ak > 0. Inoltre

50 Per 0 < h < k e 1 < a < b :

51 Useremo le notazioni asintotiche anche all’interno delle formule.
In questo caso le notazioni O(f(n)), Ω(f(n)) e ϴ(f(n)) stanno ad indicare una qualche funzione appartenente a tali insiemi e di cui non ci interessa conoscere la forma esatta ma solo il comportamento asintotico. Ad esempio T(n)=n2+O(n) significa che T(n) è la somma di n2 e di una funzione che cresce al più linearmente.

52 Valutare la difficoltà dei problemi
esiste un algoritmo che risolve il problema con questa complessità limite superiore: O(n2)

53 Valutare la difficoltà dei problemi
ogni algoritmo che risolve il problema ha complessità maggiore o uguale di questa limite inferiore: (n)

54 Un limite superiore per il problema dell’ordinamento
Abbiamo visto che Insert-Sort per ordinare n oggetti richiede O(n2) operazioni Quindi O(n2) è un limite superiore

55 Vedremo in seguito che (n log n) è un limite stretto per il problema dell’ordinamento.
Per ora ci limitiamo a dimostrare che: La complessità nel caso pessimo di ogni algoritmo di ordinamento sul posto che confronta e scambia tra loro soltanto elementi consecutivi dell’array è (n2). Quindi il problema di ordinare sul posto un array scambiando tra loro soltanto elementi consecutivi ha complessità (n2).

56 Se l’array è ordinato non ci sono inversioni.
Sia A[1..n] un array Se i < j e A[i] > A[j] diciamo che la coppia di indici (i, j) è una inversione i j k 8 3 3 Se l’array è ordinato non ci sono inversioni. Se l’array è ordinato in senso opposto e gli elementi sono tutti distinti allora ogni coppia (i, j) di indici con i < j è una inversione e quindi ci sono esattamente n(n-1)/2 inversioni.

57 Come cambia il numero di inversioni quando facciamo uno scambio tra due elementi consecutivi A[i] ed A[i+1] dell’array? i i+1 x y Consideriamo tutte le coppie di indici ( j, k) con j < k e vediamo quante e quali di esse possono cambiare di stato da inversioni a non inversioni o viceversa quando scambiamo A[i] con A[i+1].

58 Se j e k sono entrambi diversi da i e i+1 la coppia ( j, k) non cambia di stato e quindi il numero di inversioni di questo tipo non cambia. j i i+1 k u y x v

59 Consideriamo le due coppie (i, k) e (i+1, k) con k > i+1 ossia
x y v i i+1 k y x v (i, k) è inversione dopo lo scambio se e solo se (i+1, k) lo era prima e (i+1, k) è inversione se e solo se (i, k) lo era prima. Quindi le due coppie si scambiano gli stati ma il numero totale di inversioni non cambia.

60 Consideriamo le coppie (j, i) e (j,i+1) con j < i ossia
u x y j i i+1 u y x La situazione è simmetrica di quella precedente e quindi anche in questo caso il numero totale di inversioni non cambia.

61 Rimane soltanto da considerare la coppia (i, i+1) che con lo scambio cambia di stato se i due elementi sono diversi. In conclusione con lo scambio di due elementi consecutivi dell’array il numero totale di inversioni aumenta o diminuisce di 1 (o rimane invariato se i due elementi scambiati erano uguali).

62 Nel caso pessimo in cui l’array è ordinato in senso inverso e gli elementi sono tutti distinti le inversioni iniziali sono n(n-1)/2. Occorrono quindi almeno n(n-1)/2 scambi tra elementi consecutivi per ridurre tale numero a 0. Siccome n(n-1)/2 = (n2) rimane dimostrato il limite inferiore.

63 Esercizio: Abbiamo dimostrato che scambiando due elementi diversi consecutivi il numero totale di inversioni aumenta o diminuisce di 1. Quindi se prima dello scambio il numero di inversioni totale era pari, dopo lo scambio esso risulta dispari e viceversa. Mostrare che questo cambiamento della parità del numero totale di inversioni avviene anche se si scambiano due elementi diversi non consecutivi.

64 Soluzione delle ricorrenze
Metodo di sostituzione: Si assume che la soluzione sia di un certo tipo, ad esempio dove k1, k2 e k3 sono delle costanti Si sostituisce la soluzione nella ricorrenza e si cercano dei valori delle costanti per i quali la ricorrenza è soddisfatta. Se le cose non funzionano si riprova con un altro tipo di soluzione.

65 Esempio: assumiamo sostituendo si ottiene: Le costanti k1, k2 e k3 devono essere le stesse a sinistra e a destra.

66 per n = 1 si ottiene: mentre per n > 1 da cui e dunque è la soluzione.

67 Soluzione delle ricorrenze
Metodo dell’esperto: Fornisce direttamente le soluzioni asintotiche di molte ricorrenze del tipo: dove n/b significa anche n/b o n/b

68 Teorema dell’esperto:
Se T(n) = aT(n/b)+f(n) è una ricorrenza con a ≥ 1 e b > 1 costanti e dove n/b può essere anche n/b o n/b allora : per qualche costante  > 0 per qualche costante ε > 0 ed esistono k < 1 ed N tali che a f(n/b)  k f(n) per ogni n  N

69 Intuizione:

70 Come usare il Teorema dell’esperto
T(n) = aT(n/b)+f(n) Togliere eventuali arrotondamenti per eccesso o per difetto Calcolare Calcolare il limite Se il limite è finito e diverso da 0 siamo nel Caso 2 e

71 Se il limite è 0 potremmo essere nel Caso 1
Se il limite è 0 potremmo essere nel Caso 1. Per esserne sicuri occorre trovare un valore positivo ε per il quale risulti finito il limite nel qual caso possiamo concludere Se per ogni ε positivo tale limite risulta infinito il teorema dell’esperto non si può usare.

72 Se il limite è ∞ potremmo essere nel Caso 3
Se il limite è ∞ potremmo essere nel Caso 3. Per esserne sicuri occorre trovare un ε positivo per il quale risulti diverso da 0 il limite Se è 0 per ogni ε positivo non si può usare il teorema dell’esperto. Altrimenti prima di concludere bisogna studiare la disequazione a f(n/b) ≤ k f(n)

73 Se tale disequazione è soddisfatta per qualche costante k strettamente minore di 1 e per tutti i valori di n da un certo valore N in poi possiamo concludere che Altrimenti il teorema dell’esperto non si può usare.

74 Esempi: Trascurando gli arrotondamenti entrambe sono della forma: Con a=b=2 ed f(n)=(n) siccome e quindi possiamo applicare il Caso 2 e concludere

75 Esempio: In questo caso e quindi Caso 1? Se Per e Quindi e si applica il Caso 1

76 Esempio: In questo caso e quindi Caso 3? Se e Per e Quindi Inoltre Si applica il Caso 3:

77 Esempio: ma e quindi per qualunque Dunque non si può usare il metodo dell’esperto. Neanche la seconda condizione è soddisfatta ma e quindi non esiste nessun k < 1 tale che per ogni n > N

78 Soluzione: Algoritmo Heap-Sort
Un array A[1..n] può essere interpretato come un albero binario: A[1] è la radice, A[2i] e A[2i+1] sono i figli di A[i] A[ i / 2 ] è il padre di A[i]

79 Albero binario quasi completo a1 a2 a12 a3 a4 a5 a6 a7 a8 a9 a10 a11
a1 a2 a12 a3 a4 a5 a6 a7 a8 a9 a10 a11 Albero binario quasi completo a10 a11 a9 a12 a2 a8 a4 a5 a6 a7 a3 a1 12 102 112 1002 1012 1102 1112 10002 10012 10102 10112 11002

80 Proprietà di un heap (mucchio)
Diciamo che A[1..n] è un (è ordinato a) max-heap se ogni elemento A[i] soddisfa la seguente proprietà: “A[i] è maggiore o uguale di ogni suo discendente in A[1..n]” Per brevità indicheremo questa proprietà con H(i)

81 Un max-heap 9 8 2 7 5 4 3 6 1 6 1 3 2 8 4 5 7 9 1 2 3 4 5 6 7 8 9 10 11 12

82 Costruzione di un max-heap
6 5 5 6 4 8 9 2 7 3 1 9 5 6 8 5 9 6 9 8 5 8 7 7 6 2 9 2 4 2 5 1 6 2 3 8 9 2 7 4 5 6 7 4 3 7 1 4 8 9 10 11 12

83 Ordinamento dell’array
8 2 2 9 6 3 1 8 1 7 6 3 7 3 7 1 1 7 9 8 2 7 5 4 3 6 1 5 3 1 6 3 5 4 3 3 4 3 2 7 3 1 4 1 2 6 2 1 2 1 9 1 8 7 2 3 5 7 4 4 5 6 7 4 3 6 1 2 8 9 10 11 12

84 Max-Heapfy(A,i) l = 2i, r =2i+1 m = i if l  A.heapsize and A[l] > A[m] m = l if r  A.heapsize and A[r] > A[m] m = r if m  i t = A[i], A[i] = A[m], A[m] = t Max-Heapfy(A,m)

85 Build-Max-Heap (A) A.heapsize = A.length for i = A.lenght/2 downto 1 Max-Heapfy(A,i)

86 Heap-Sort (A) Build-Max-Heap(A) for i = A.length downto 2 t = A[i], A[i] = A[1], A[1] = t A.heapsize = A.heapsize - 1 Max-Heapfy(A,1)

87 Abbiamo visto che la complessità nel caso pessimo di ogni algoritmo di ordinamento sul posto che confronta e scambia tra loro elementi consecutivi dell’array è (n2). Per ottenere algoritmi più efficienti dobbiamo quindi operare confronti e scambi tra elementi “distanti” dell’array. L’algoritmo Heap-Sort confronta elementi non consecutivi e possiamo quindi sperare che la sua complessità sia minore.

88 In effetti Heap-Sort richiede tempo O(n log n) per ordinare un array di n elementi (vedi Libro 6.2, 6.3, 6.4)

89 Implementazione di code con priorità
Gli heap binari si possono usare, oltre che per ordinare un array, anche per implementare delle code con priorità. Le code con priorità sono delle strutture dati in cui è possibile immagazzinare degli oggetti x a cui è attribuita una priorità x.key ed estrarli uno alla volta in ordine di priorità.

90 Le operazioni fondamentali sulle code con priorità sono:
Insert(S, x): aggiunge x alla coda S Maximum(S): ritorna x  S con x.key massima Extract-Max(S): toglie e ritorna x  S con x.key massima. Possono inoltre essere definite anche: Increase-Key(S,x,p): aumenta la priorità di x Change-Key(S,x,p): cambia la priorità di x

91 Heap-Maximum(A) // A è un max-heap
if A.heapsize < 1 error “underflow” else return A[1] Heap-Extract-Max(A) // A è un max-heap if A.heapsize < 1 error “underflow” else max = A[1] A[1] = A[A.heapsize] A.heapsize = A.heapsize - 1 Max-Heapfy(A,1) return max

92 Heap-Insert 12 9 9 12 8 7 12 7 5 7 4 12 4 4 12 13 3 6 1 2 6 1 3 2 8 4 5 7 9 12 10 11

93 Per realizzare Heap-Insert e Heap-Increase-Key ci serve una Max-Heapfy diversa che invece della proprietà: “A[i] è maggiore o uguale di ogni suo discendente” usa la proprietà simmetrica: “A[i] è minore o uguale di ogni suo ascendente” entrambe, se vere per ogni elemento dell’array, ci assicurano l’ordinamento a max-heap di A.

94 La nuova versione Max-HeapfyR ricostruisce lo heap quando tutti gli elementi dell’array sono minori o uguali dei loro ascendenti tranne al più quello in posizione i. Max-HeapfyR(A,i) // solo A[i] può non soddisfare la proprietà while i >1 and A[⌊i/2⌋].key < A[i].key scambia A[⌊i/2⌋] con A[i] // solo A[⌊i/2⌋] può non soddisfarla i = ⌊i/2⌋

95 Heap-Increase-Key(A,i,p) // A max-heap
if p < A[i].key error “la nuova priorità è minore” else A[i].key = p Max-HeapfyR(A,i) Max-Heap-Insert(A,x) // A max-heap A.heapsize = A.heapsize+1 A[A.heapsize] = x Max-HeapfyR(A,A.heapsize)

96 Possiamo facilmente realizzare anche una Heap-Change-Key nel modo seguente:
Heap-Change-Key(A,i,p) // A max-heap if p < A[i].key A[i].key = p Max-Heapfy(A,i) else Max-HeapfyR(A,i)

97 Soluzione 6: Algoritmo Quicksort
Si basa sulla partizione dell’array rispetto ad un suo elemento scelto come “pivot”. L’operazione viene quindi ripetuta sulle due parti così ottenute. 9 9 6 4 6 9 2 8 8 4 4 3 6 4 1 2 9 2 7 5 7 4 8 4 3 3 6 7 7 1 1 9 5 7 5 i j i i j i i j j i i j j j j j j j

98 Quicksort(A,p,r) if p < r q = Partition(A,p,r) Quicksort(A,p,q-1)
Quicksort(A,q+1,r) 1 p r n A non ordinati 1 p r n A q 1 p r n A q 1 p r n A q 1 p r n A ordinati

99 Partition(A,p,r) x = A[r] i = p -1 for j = p to r -1 if A[j] < x
i = i+1 scambia A[i] e A[j] scambia A[i+1] e A[r] return i+1 1 p r n A 1 p r n A i x 1 p r n A i x j 1 p r n A i x 1 p r n A i x

100 Array ordinato o ordinato in senso inverso
Quicksort (A,p,r) // Complessità massima if p < r q = Partition(A,p,r) Quicksort(A,p,q-1) Quicksort(A,q+1,r) Array ordinato o ordinato in senso inverso

101 Quicksort(A,p,r) // Complessità minima
if p < r // q = Partition(A,p,r) // Quicksort(A,p,q-1) // Quicksort(A,q+1,r) //

102 Quicksort (A,p,r) // Complessità media
if p < r then q = Partition(A,p,r) Quicksort(A,p,q-1) Quicksort(A,q+1,r)

103 Per n > 1 e moltiplicando per n otteniamo Per n = 2 Per n > 2 e dunque

104 dividendo per n(n+1) ponendo otteniamo

105 la cui soluzione è

106 Infine Quindi

107 La complessità media O(n log n) di Quick-Sort vale soltanto se tutte le permutazioni dell’array in ingresso sono ugualmente probabili. In molte applicazioni pratiche questo non è vero!!! Vi sono applicazioni in cui le permutazioni quasi ordinate sono molto più probabili e questo può aumentare la complessità media fino ad O(n2).

108 Randomized-Partition(A,p,r)
i = Random(p,r) scambia A[i] e A[r] return Partition(A,p,r) Randomized-Quicksort(A,p,r) if p < r then q = Randomized-Partition(A,p,r) Randomized-Quicksort(A,p,q-1) Randomized-Quicksort(A,q+1,r)

109 Complessità del problema
Problema dell’ordinamento Input: sequenza a1,a2,...,an di elementi su cui è definito un ordine Output: a'1,a'2,...,a'n permutazione di a1,a2,...,an tale che a'1 ≤ a'2 ≤ ... ≤ a'n Se non facciamo ipotesi sul tipo degli elementi della sequenza le uniche operazioni permesse sono confronti e assegnazioni.

110 Siccome siamo interessati ad un limite inferiore possiamo contare solo alcune delle operazioni.
Se un certo limite inferiore vale per il tempo richiesto per eseguire tali operazioni a maggior ragione varrà per il tempo calcolo totale. Noi conteremo solo i confronti e dimostreremo che nel caso pessimo il numero di confronti è Ω(n log n). Per fare questo è utile rappresentare la struttura di un algoritmo mediante un albero delle decisioni.

111 1:2 ≤ > 2:3 1:3 ≤ > ≤ > 1:3 2:3 ≤ > > ≤
Esempio. Albero delle decisioni di Insertion-Sort con un array di 3 elementi. Insertion-Sort(A) n = A.length for j = 2 to n i = j – 1 while i ≥ 1 and A[i]>A[i+1] scambia A[i] con A[i+1] i = i – 1 1:2 > 2:3 1:3 > > 1:3 2:3 > >

112 1:2 a,b,c 2:3 a,b,c 1:3 b,a,c 1:3 a,c,b 2:3 b,c,a
Esempio. Albero delle decisioni di Insertion-Sort con un array di 3 elementi. 1:2 a,b,c > 2:3 a,b,c 1:3 b,a,c > > 1:3 a,c,b 2:3 b,c,a a,b,c (1,2,3) b,a,c (1,2,3) > > a,c,b (1,2,3) c,a,b (1,2,3) b,c,a (1,2,3) c,b,a (1,2,3) Se l’algoritmo è corretto le foglie devono essere etichettate con ogni permutazione possibile dell’input. Perché?

113 Le permutazioni di 1,2,. ,n sono n
Le permutazioni di 1,2,...,n sono n! e quindi l’albero delle decisioni deve avere almeno n! foglie. Ma un albero binario con N foglie deve avere altezza almeno pari a log2(N). Esercizio: Dimostrarlo per induzione su N. Dunque nel caso pessimo l’algoritmo deve eseguire almeno log2(n!) confronti.

114 Ma e quindi per ogni algoritmo generale di ordinamento. Possiamo concludere che Ω(n log n) è un limite inferiore per la complessità del problema dell’ordinamento.

115 L’algoritmo di ordinamento Heapsort risolve il problema dell’ordinamento con complessità massima
Dunque O(n log n) è limite superiore per la complessità del problema dell’ordinamento. Siccome limite superiore e inferiore coincidono (n log n) è limite stretto per il problema dell’ordinamento.

116 Considerazione sul limite inferiore Ω(n log n) per l’ordinamento
ATTENZIONE: Il limite inferiore Ω(n log n) da noi dimostrato vale solo per algoritmi di ordinamento generali, ossia algoritmi che non fanno alcuna ipotesi sul tipo degli elementi da ordinare: le uniche operazioni ammesse su tali elementi sono confronti e assegnazioni.

117 Il limite inferiore Ω(n log n) vale anche per ordinare numeri reali sui quali, oltre a confronti ed assegnazioni, si possono usare anche le quattro operazioni aritmetiche. In questo caso la dimostrazione del limite inferiore è molto più difficile e si basa su alcuni risultati di geometria algebrica. La dimostrazione si può trovare nel testo di Geometria Computazionale di F. Preparata.

118 Ordinamento in tempo lineare
Il limite inferiore Ω(n log n) vale per tutti gli algoritmi di ordinamento generali, ossia per algoritmi che non fanno alcuna ipotesi sul tipo degli elementi della sequenza da ordinare. Se facciamo opportune ipotesi restrittive sul tipo degli elementi possiamo trovare algoritmi più efficienti. Naturalmente il limite inferiore banale Ω(n) vale comunque per tutti gli algoritmi di ordinamento.

119 Algoritmo Counting-Sort
Assume che gli elementi dell’array siano interi compresi tra 0 e k con k costante. Per ordinare un array A Counting-Sort richiede un secondo array B in cui mette la sequenza ordinata e un array ausiliario C[0..k].

120 1 2 3 4 5 6 7 8 A 1 4 2 1 2 2 C 2 3 1 4 C 2 4 7 8 1 3 1 2 3 4 5 7 6 8 B 1 2 3 4 5 6 7 8 1 1 2 2 2 4

121 Counting-Sort(A,B,k) // A contiene a1,...,an
for i = 0 to k C[i] = 0 for j = 1 to A.length x = A[j], C[x] = C[x] + 1 // C[x] è il numero di elementi aj = x for i = 1 to k C[i] = C[i] + C[i-1] // C[x] è il numero di elementi aj ≤ x for j = A.length downto 1 // i = C[x] è la posizione in B dove // mettere il prossimo aj = x x = A[j], i = C[x], B[i] = x C[x] = C[x] - 1

122 Complessità: TCS(n,k) = (n+k)
Counting-Sort(A,B,k) // Complessità for i = 0 to k // C[i] = // for j = 1 to A.length // x = A[j], C[x] = C[x] // for i = 1 to k // C[i] = C[i] + C[i-1] // for j = A.length downto // x = A[j], i = C[x], B[i] = A[j] // C[x] = C[x] // Complessità: TCS(n,k) = (n+k) Se k = O(n) allora TCS(n,k) = (n)

123 Osservazione: Nell’ultimo ciclo for dell’algoritmo gli elementi dell’array A vengono copiati nell’array B partendo dall’ultimo Cosa succede se partiamo dal primo?

124 A 1 4 2 1' 2' 0' 2" 3 5 6 7 8 C 2 4 7 8 1 3 1 3 6 7 B 1 2 3 4 5 6 7 8 1' 1 2 4

125 Succede che l’algoritmo è ancora corretto ma gli elementi uguali vengono ricopiati in ordine inverso. Quando un algoritmo di ordinamento mantiene l’ordine iniziale tra due elementi uguali si dice che esso è stabile. L’algoritmo Counting-Sort (con l’ultimo ciclo for decrescente) è stabile.

126 Algoritmo Radix-Sort Assume che i valori degli elementi dell’array siano interi rappresentabili con al più d cifre in una certa base b. Ad esempio interi di d = 5 cifre decimali (b = 10), interi di d = 4 byte (cifre in base b = 256) o stringhe di d caratteri (b = 256). Per ordinare l’array si usa d volte un algoritmo di ordinamento stabile (ad esempio Counting-Sort) per ordinare l’array rispetto a ciascuna delle d cifre partendo dalla meno significativa.

127 4 3 2 1 7 5 6 9 8 4 3 2 1 7 5 6 9 8 4 3 2 1 7 5 6 9 8 4 3 2 1 7 5 6 9 8 4 3 2 1 7 5 6 9 8 A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8]

128 Radix-Sort(A,d) // A[i] = cd...c2c1
for j = 1 to d // A è ordinato rispetto alle cifre cj-1...c1 “usa un algoritmo stabile per ordinare A rispetto alla j-esima cifra” // A è ordinato rispetto alle cifre cj...c1 // A è ordinato

129 Radix-Sort(A,d) // Complessità
for j = 1 to d “usa Counting-Sort per ordinare A rispetto alla j-esima cifra” Complessità: dove b è la base della numerazione e d è il numero di cifre dei numeri da ordinare.

130 Dovendo ordinare n numeri di m bit ciascuno possiamo scegliere r < m e suddividere i numeri in d=m/r “cifre” di r bit ciascuna In questo caso la base della numerazione è b=2r e Radix-Sort richiede tempo La funzione ha un minimo per r tale che Quindi il valore ottimo di r dipende soltanto da n ed è approssimativamente log2 n.


Scaricare ppt "Algoritmi e Strutture Dati Laurea in Informatica"

Presentazioni simili


Annunci Google