Escolar Documentos
Profissional Documentos
Cultura Documentos
Problema: ordinamento
Input: una sequenza A di n numeri a1, a2, ... an
Output: una permutazione b1, b2, ... bn della sequenza di input
tale che b1 b2 ... bn
INSERTION-SORT(A)
1 for j := 2 to A.length
2
key := A[j]
i := j 1
A[i + 1] := A[i]
i := i 1
A[i + 1] := key
pseudocodice
assegnamento: i := j
assegnamento multiplo: i := j := e
applicato da destra a sinistra
cio la stessa cosa che scrivere j := e; i := j
A[i + 1] := A[i]
i := i 1
A[i + 1] := key
A[i + 1] := A[i]
i := i 1
}
A[i + 1] := key
pseudocodice (2)
Modello di computazione
costo
1 for j := 2 to A.length
c1
c2
key := A[j]
i := j 1
A[i + 1] := A[i]
i := i 1
A[i + 1] := key
0
c4
c5
c6
c7
c8
numero
di volte
n
n-1
n-1
n-1
j= 2 t j
n
n-1
Note:
n = A.length = dimensione dei dati in ingresso
t2, t3 ... tn = numero di volte che la condizione del ciclo
while viene eseguita quando j = 2, 3, ... n
S!
Merge sort
Idea dell'algoritmo:
se l'array da ordinare ha meno di 2 elementi, ordinato per
definizione
altrimenti:
si divide l'array in 2 sottoarray, ognuno con la met degli elementi di
quello originario
si ordinano i 2 sottoarray ri-applicando l'algoritmo
si fondono (merge) i 2 sottoarray (che ora sono ordinati)
Un esempio di funzionamento:
42
16
28
36
26
78
84
42
16
28
36
26
78
84
42
16
28
36
26
78
84
16
42
28
36
26
78
84
16
28
36
42
26
78
84
16
26
28
36
42
78
84
9
pseudocodice di MERGE-SORT
MERGE-SORT(A, p, r)
1
if p < r
q := (p + r)/2
MERGE-SORT(A, p, q)
MERGE-SORT(A, q+1, r)
MERGE(A, p, q, r)
Per completare l'algoritmo dobbiamo definire un sottoalgoritmo MERGE che "combina" le soluzioni dei problemi pi
piccoli
10
Idea dell'algoritmo:
pseudocodice:
MERGE (A, p, q, r)
1 n1 := q p + 1
2 n2 := r q
3 crea (alloca) 2 nuovi array L[1..n1+1] e R[1..n2+1]
4 for i := 1 to n1
5
L[i] := A[p + i - 1]
6 for j := 1 to n2
7
R[j] := A[q + j]
8 L[n1 + 1] :=
9 R[n2 + 1] :=
10 i := 1
11 j := 1
12 for k := p to r
13
if L[i] R[j]
14
A[k] := L[i]
15
i := i + 1
16
else A[k] := R[j]
17
j := j + 1
11
12
Pi in generale:
Complessit di un algoritmo divide et impera
Dn + aTn / b + Cn altrimenti
2Tn / 2+ n altrimenti
13
Complessit di MERGE-SORT
2Tn / 2+ cn altrimenti
log 2 n
cn/4
cn
cn/2
cn/4
cn/4
cn/4
c c c c c c c c c c c c c
cn
cn
Totale: cnlogn+ cn
14
42
16
28
36
26
78
84
F1
16
42
28
36
26
78
84
F2
16
28
36
42
26
78
84
F1
16
26
28
36
42
78
84
F2
15
Risoluzione di ricorrenze
16
18
cn
T n / 3
Espandiamo:
T 2n / 3
cn
cn /3
T n / 9
2cn/3
T 2n / 9
T 2n / 9 T 4n / 9
fino in fondo:
cn
cn
cn /3
log 3 / 2 n
cn/9
cn
2cn/3
2cn/9
2cn/9
4cn/9
cn
20
21
Data la ricorrenza:
T(n) = aT(n/b) + f(n)
Alcune osservazioni...
La soluzione data dal pi grande tra nlogba e f(n)
se nlogba il pi grande, T(n) (nlogba)
se f(n) il pi grande, T(n) (f(n))
se sono nella stessa classe secondo la relazione , T(n)
(f(n)log(n))
(1)
T (n) a T (n i ) cn k
1ih
a=
se n m h
se n m
ai
1 i h
in cui poniamo
allora abbiamo che:
1. se a=1, allora T(n)=O(nk+1)
2. se a 2, allora T(n)=O(annk)
23
Grafi (richiamo)
Un cammino una sequenza di nodi [v0, v1, v2, vn] tali che
tra ogni coppia di nodi della sequenza (vi, vi+1) c' un arco
i nodi v0, vn appartengono al cammino
la lunghezza del cammino data da n (numero di vertici -1)
24
Alberi (richiamo)
radice
profondit 0
nodi interni
profondit 1
altezza = 3
profondit 2
profondit 3
foglie
Chiamiamo:
25
HEAPSORT
Esempio:
1
10 11 12
a8
8
a4
a1
a2
a5
a6
a9
a10
a11
10
11
a3
7
a7
a12
12
26
Esempio:
1
10 11 12
4
8
8
5
10
11
2
12
27
Operazioni di base:
PARENT(i)
1 return i/2
LEFT(i)
1 return 2*i
RIGHT(i)
1 return 2*i + 1
Algoritmi di supporto
6
7
8 if max i then
9
swap A[i] A[max]
10
MAX-HEAPIFY(A, max)
29
Osservazione
20
21
22
23
24
30
31
Costo di BUILD-MAX-HEAP?
ad occhio, ogni chiamata a MAX-HEAPIFY costa
O(log(n)), e vengono fatte n/2 chiamate (con n che
A.length), quindi il costo O(n log(n))
ma in realt questo limite non stretto...
Osserviamo che:
l'altezza di un albero quasi completo di n nodi log2(n)
se definiamo come altezza di un nodo di uno heap la
lunghezza del cammino pi lungo che porta ad una foglia,
il costo di MAX-HEAPIFY invocato su un nodo di altezza
h O(h)
il numero massimo di nodi di altezza h di uno heap
n/2h+1
Quindi MAX-HEAPIFY viene invocato n/2h+1 volte ad ogni
altezza h, quindi il costo di BUILD-MAX-HEAP
lgn h
2h+1 O(h) = O n 2h
h=0
h=0
lgn
h
1/ 2
=
=2
h
2
(1 1/ 2 )
h=0 2
32
Infatti:
1
x =
( 1 x)
h=0
h
h
1
d x =
(
1
x)
h=0
hx h1 = 1
2
dx
( 1 x)
h=0
x
h
hx =
2
( 1 x)
h=0
h
1/ 2
=
=2
h
2
(1 1/ 2 )
h=0 2
Oppure: esercizio (per induzione)
h
n2
= 2 n
h
2
h=1 2
n
33
HEAPSORT
34
QUICKSORT
35
PARTITION
i=j
p
1
p
1
p
1
j
p
1
8
36
37
38
(n)
n
0
n-1
(n-1)/2 - 1
(n-1)/2
39
n
0
n-1
(n-1)/2 - 1
(n-1)/2
40
41
a1 , a2 ,
an
a1 >= a2
a1 < a2
a5 , a8 ,
a23
a12, a21,
a6
42
n n
n! n(n 1)(n 2)
2 2
e quindi ,
n n
log( n!) log
2 2
n
2
COUNTING-SORT
pseudocodice
parametri: A l'array di input (disordinato), B conterr gli
elementi ordinati (cio l'output), e k il massimo tra i valori di
A
A e B devono essere della stessa lunghezza n
COUNTING-SORT (A, B, k)
1 for i := 0 to k
2
C[i] := 0
3 for j := 1 to A.length
4
C[A[j]] := C[A[j]] + 1
5 //C[i] ora contiene il numero di elementi uguali a i
6 for i := 1 to k
7
C[i] := C[i] + C[i - 1]
8 //C[i] ora contiene il numero di elementi i
9 for j := A.length downto 1
10
B[C[A[j]]] := A[j]
11
C[A[j]] := C[A[j]] - 1
44
Se A = 2,5,3,0,2,3,0,3
A.length = 8
B deve avere lunghezza 8
Se eseguiamo COUNTING-SORT(A, B, 5)
prima di eseguire la linea 5 (cio alla fine del loop 3-4)
C = 2,0,2,3,0,1
prima di eseguire la linea 8 C = 2,2,4,7,7,8
dopo le prime 3 iterazioni del ciclo 9-11 abbiamo
1. B = _,_,_,_,_,_,3,_ , C = 2,2,4,6,7,8
2. B = _,0,_,_,_,_,3,_ , C = 1,2,4,6,7,8
3. B = _,0,_,_,_,3,3,_ , C = 1,2,4,5,7,8
La complessit globale (n + k)
Se k O(n), allora il tempo di esecuzione O(n)
lineare!
Strutture Dati
46
47
INSERT(S, x)
inserisce l'oggetto x nella collezione S
un'operazione che modifica la collezione
DELETE(S, x)
cancella l'oggetto x dalla collezione S (op. di modifica)
MINIMUM(S)
restituisce l'oggetto nella collezione con la chiave pi
piccola (op. di interrogazione)
MAXIMUM(S)
restituisce l'oggetto nella collezione con la chiave pi
grande (op. di interrogazione)
SUCCESSOR(S, x)
restituisce l'oggetto che segue x nella collezione, secondo
una qualche relazione di ordinamento (op. di
interrogazione)
per esempio, potrebbe essere l'elemento con la prossima
chiave pi grande, se c' un ordinamento sulle chiavi
potrebbe essere qualcosa d'altro (la sua definizione
dipende dalla specifica struttura dati)
PREDECESSOR(S,x)
restituisce l'oggetto che precede x nella collezione, secondo
una qualche relazione di ordinamento (op. di
interrogazione)
48
Pile (Stack)
Una pila gestita con una politica LIFO (Last In First Out)
l'elemento che viene cancellato (pop) quello che stato inserito
per ultimo (cio quello che nella pila da meno tempo)
cio, se viene fatta una PUSH di un oggetto e su una pila S, seguita
immediatamente da una POP su S, l'elemento restituite dalla POP lo
stesso e di cui era stata fatta la PUSH
se S.top = t, allora S[1], S[2], ... S[t] contengono tutti gli elementi, e S[1]
stato inserito prima di S[2], che stato inserito prima di S[3], ecc.
49
50
51
52
53
tempo di esecuzione:
ENQUEUE(Q, x)
T(n) = O(1)
1 Q[Q.tail] := x
2 if Q.tail = Q.length
3
Q.tail := 1
4 else Q.tail := Q.tail + 1
tempo di esecuzione:
DEQUEUE(Q)
T(n) = O(1)
1 x := Q[Q.head]
2 if Q.head = Q.length
3
Q.head := 1
4 else Q.head := Q.head + 1
5
return x
e i controlli di coda piena/vuota?
(in realt c un po di ridondanza )
54
key
16
next
4
55
56
57
58
Operazioni (3)
Cancellazione
input: la lista L, e l'oggetto x da
cancellare
si noti che non si passa come
argomento la chiave da cancellare,
ma tutto l'oggetto
output: cancella x dalla lista
LIST-DELETE(L, x)
1 if x.prev NIL
2
x.prev.next := x.next
3 else L.head := x.next
4 if x.next NIL
5 x.next.prev := x.prev
//differenze tra C e Java?
T(n) = O(1)
59
Operazioni (4)
NB
60
0 NIL
U
18
4
2
1 NIL
7
...
T
2
dati
dati
3
4 NIL
61
62
Tabelle hash
Una tabella hash usa una memoria proporzionale al
numero di chiavi effettivamente memorizzate nel
dizionario
indipendentemente dalla cardinalit dell'insieme
U di chiavi
Idea fondamentale: un oggetto di chiave k
memorizzato in tabella in una cella di indice h(k),
con h una funzione hash
se m la dimensione della tabella, h una
funzione
h: U {0..m-1}
la tabella T ha m celle, T[0], T[1], ... , T [m-1]
h(k) il valore hash della chiave k
Problema: ho |U| possibili chiavi ed una funzione
che le deve mappare su un numero m (< |U|, ma
tipicamente << |U|) di slot della tabella
necessariamente avr delle chiavi diverse
(tante!) k1, k2 tali che h(k1)=h(k2)
in questo caso ho delle collisioni
Ci sono diverse tecniche per risolvere le collisioni
Una tipica quella del concatenamento (chaining)
63
0 NIL
1
K
k2
T
k2
k3
2 NIL
k3
3
k5
k5
4 NIL
5 NIL
64
0 NIL
K
k2
k2
k3
2 NIL
k3
3
k5
k5
4 NIL
5 NIL
E [ n j ]=
1
n
n
=
=
i
m i= 1
m
68
69
70
A ( 5 1) / 2
l'inverso della sezione aurea
se si vuole applicare il calcolo precedente,
occorre prendere come A la frazione della
forma s/2w pi vicina all'inverso della
sezione aurea
dipende dalla lunghezza della parola w
71
Indirizzamento aperto
Un altro modo di evitare collisioni tramite la tecnica
dell'indirizzamento aperto
In questo caso la tabella contiene tutte le chiavi, senza
memoria aggiuntiva
quindi il fattore di carico non potr mai essere pi
di 1
L'idea quella di calcolare l'indice dello slot in cui va
memorizzato l'oggetto; se lo slot gi occupato, si cerca
nella tabella uno slot libero
la ricerca dello slot libero per non viene fatta in
ordine 0,1,2,...,m-1; la sequenza di ricerca (detta
sequenza di ispezione) un valore calcolato dalla
funzione hash
dipende anche dalla chiave da inserire
la sequenza deve essere esaustiva, deve coprire
tutte le celle
La funzione hash ora diventa:
h : U {0,1,..., m-1} {0,1,..., m-1}
la sequenza di ispezione h(k, 0), h(k, 1),..., h(k, m1) deve essere una permutazione di 0, ... ,m-1
72
HASH-INSERT(T, k)
1 i := 0
2 repeat
3
j := h(k, i)
4
if T[j] = NIL
5
T[j] := k
6
return j
7
else i := i + 1
8 until i = m
9 error hash table overflow
73
75
76
77
78
Ispezione quadratica
Nel caso dell'ispezione quadratica:
h(k,i) = (h'(k)+c1i+c2i2) mod m
c1 e c2 sono costanti ausiliarie (con c2 0)
c1 e c2 non possono essere qualsiasi, ma
devono essere scelte in modo che la
sequenza percorra tutta la tabella
ancora una volta, la posizione di ispezione
iniziale determina tutta la sequenza, quindi
vengono prodotte m sequenze di ispezione
distinte
soffre del fenomeno dell'addensamento
secondario: chiavi con la stessa posizione
iniziale danno luogo alla stessa sequenza di
ispezione
79
Doppio hashing
Doppio hashing:
h(k,i) = (h1(k)+i h2(k)) mod m
h1 e h2 sono funzioni hash ausiliarie
perch la sequenza prodotta sia una
permutazione di 0, ... ,m-1 h2(k) deve
essere primo rispetto a m (non deve avere
divisori comuni tranne l'1)
posso ottenere questo prendendo come
m una potenza di 2, e facendo in modo
che h2 produca sempre un valore dispari
oppure prendendo come m un numero
primo, e costruendo h2 in modo che
restituisca sempre un valore < m
esempio:
h1(k) = k mod m
h2(k) = 1 + (k mod m')
con m' < m (per esempio m' = m-1)
numero di sequenze generate ora (m2) in
quanto ogni coppia (h1(k), h2(k)) produce
una sequenza di ispezione distinta
80
Alberi binari
Un albero binario fatto di 3 elementi: un
nodo radice; un albero binario che il
sottoalbero sinistro, ed un albero binario che
il sottoalbero destro
una definizione ricorsiva
un sottoalbero pu essere vuoto (NIL)
Ad ogni nodo dell'albero associamo un oggetto
con una chiave
Esempio di albero binario
radice
sottoalbero sinistro
3
2
7
1
sottoalbero destro
81
82
83
84
Esercizi:
scrivere lo pseudocodice per PREORDERTREE-WALK e POSTORDER-TREE-WALK
scrivere lo pseudocodice per Breadthfirst-TREE-WALK (il cui
risultato per lalbero
precedente deve essere:
5, 3, 7, 2, 5, 8)
87
88
TREE-MAXIMUM(x)
1 while x.right NIL
2
x := x.right
3 return x
89
Inserimento (1)
Idea di base per l'inserimento: scendere
nell'albero fino a che non si raggiunge il posto
in cui il nuovo elemento deve essere inserito,
ed aggiungere questo come foglia
Supponiamo, per esempio, di volere inserire un
nodo con chiave 7 nell'albero seguente:
91
Inserimento (2)
eseguiamo i seguenti passi:
confrontiamo 5 con 7 e decidiamo che il
nuovo elemento deve essere aggiunto al
sottoalbero destro di 5
confrontiamo 8 con 7 e decidiamo che 7
deve essere aggiunto al sottoalbero sinistro
di 8
notiamo che il sottoalbero sinistro di 8
vuoto, e aggiungiamo 7 come sottoalbero
sinistro di 8
quindi, otteniamo il nuovo albero:
5
92
Insert: pseudocodice
TREE-INSERT(T, z)
1
y := NIL
2
x := T.root
3
while x NIL
4
y := x
5
if z.key < x.key
6
x := x.left
7
else x := x.right
8
z.p := y
9
if y = NIL
10
T.root := z
//l'albero T era vuoto
11 elsif z.key < y.key
12
y.left := z
13 else y.right := z
93
Cancellazione (1)
Quando cancelliamo un oggetto z da un albero, abbiamo
3 possibili casi (a seconda che z sia una foglia o un nodo
interno):
il nodo z da cancellare non ha sottoalberi
il nodo z da cancellare ha 1 sottoalbero
il nodo z da cancellare ha 2 sottoalberi
Il caso 1 quello pi facile, basta mettere a NIL il
puntatore del padre di z che puntava a z:
5
3
1
5
8
3
9
5
7
3
9
9
4
10
10
94
Cancellazione(2)
Nel caso 3 dobbiamo trovare il successore del nodo da
cancellare z, copiare la chiave del successore in z, quindi
cancellare il successore
cancellare il successore potrebbe richiedere di
spostare un (il) sottoalbero del successore un livello
su
si noti che in questo caso l'oggetto originario z non
cancellato, ma il suo attributo key viene modificato
(l'oggetto effettivamente cancellato quello con il
successore di z)
5
3
12
8
14
9
5
3
1
8
4
12
9
14
95
96
97
98
2
4
5
9
99
100
101
102
103
Esempi di alberi RB
26
17
41
14
21
10
7
16
12
19
15
30
23
47
28
20
38
35
39
17
41
14
21
10
16
12
15
19
30
23
47
28
20
38
35
39
T.nil
104
105
Rotazioni
LEFT-ROTATE(T,x)
RIGHT-ROTATE(T,y)
LEFT-ROTATE(T,x)
1 y := x.right
2 x.right := y.left
3
4
5
6
7
8
9
10
11
12
if y.left T.nil
y.left.p := x
y.p := x.p
//attacca il padre di x a y
if x.p = T.nil
T.root := y
elsif x = x.p.left
x.p.left := y
else x.p.right := y
y.left := x
//mette x a sinistra di y
x.p := y
106
RB-INSERT
L'inserimento fatto in modo analogo a quello dei BST, ma
alla fine occorre ristabilire le propriet dagli alberi RB se
queste sono state violate
per ristabilire le propriet si usa un algoritmo RB-INSERTFIXUP (che vedremo dopo)
RB-INSERT(T, z)
1
y := T.nil
2
x := T.root
3
while x T.nil
4
y := x
5
if z.key < x.key
6
x := x.left
7
else x := x.right
8
z.p := y
9
if y = T.nil
10
T.root := z
//l'albero T e' vuoto
11 elsif z.key < y.key
12
y.left := z
13 else y.right := z
14 z.left := T.nil
15 z.right := T.nil
16 z.color := RED
17 RB-INSERT-FIXUP(T, z)
Uguale a TREE-INSERT, salvo che per l'uso di T.nil al
posto di NIL e l'aggiunta delle righe 14-17
107
RB-INSERT-FIXUP
RB-INSERT-FIXUP(T, z)
1 if z = T.root
2
T.root.color = BLACK
3 else x := z.p
// x e' il padre di z
4
if x.color = RED
5
if x = x.p.left
// se x e' figlio sin.
6
y := x.p.right
// y e' lo zio di z
7
if y.color = RED
8
x.color := BLACK
// Caso 1
9
y.color := BLACK
// Caso 1
10
x.p.color := RED
// Caso 1
11
RB-INSERT-FIXUP(T,x.p)
// Caso 1
12
else if z = x.right
13
z := x
// Caso 2
14
LEFT-ROTATE(T, z)
// Caso 2
15
x := z.p
// Caso 2
16
x.color := BLACK
// Caso 3
17
x.p.color := RED
// Caso 3
18
RIGHT-ROTATE(T, x.p)
// Caso 3
19
else (come 6-18, scambiando rightleft)
Funzionamento di RB-INSERT-FIXUP
Caso 1: y rosso
quindi x.p, che anche y.p, non pu essere rosso o l'albero
originario avrebbe violato la propriet 4
x.p
x.p
x 5
x 5
7
y
z 3
z 3
x.p
x 3
z
x.p
7
9
5
x 3
7
9
z 5
109
x' = z
9 y
z 5 d
x.p
z' = x 3
9 y
5
d
9 y
z 3
x.p
x 5
z 3
5
9 y
9
d
RB-DELETE
RB-DELETE(T, z)
1
if z.left = T.nil or z.right = T.nil
2
y := z
3
else y := TREE-SUCCESSOR(z)
4
if y.left T.nil
5
x := y.left
6
else x := y.right
7
x.p := y.p
8
if y.p = T.nil
9
T.root := x
10 elsif y = y.p.left
11
y.p.left := x
12 else y.p.right := x
13 if y z
14
z.key := y.key
15 if y.color = BLACK
16
RB-DELETE-FIXUP(T,x)
17 return y
RB-DELETE-FIXUP
RB-DELETE-FIXUP(T, x)
1
x.color := BLACK
elsif x = x.p.left
w := x.p.right
if w.color = RED
// Caso 0
// x e' figlio sinistro
// w e' fratello di x
w.color := BLACK
// Caso 1
x.p.color := RED
// Caso 1
LEFT-ROTATE(T,x.p)
// Caso 1
w := x.p.right
// Caso 1
10
11
w.color := RED
// Caso 2
12
RB-DELETE-FIXUP(T,x.p)
// Caso 2
13
14
w.left.color := BLACK
// Caso 3
15
w.color := RED
// Caso 3
16
ROTATE-RIGHT(T,w)
// Caso 3
17
w := x.p.right
// Caso 3
18
w.color := x.p.color
// Caso 4
19
x.p.color := BLACK
// Caso 4
20
w.right.color := BLACK
// Caso 4
21
ROTATE-LEFT(T,x.p)
// Caso 4
112
Funzionamento di RB-DELETE-FIXUP
x 5
x 5
x 5
x 1
7 w
x 1
9
d
7 w
5 w
x 1
Caso 2: x nero, suo fratello destro w nero con figli entrambi neri
3
x
3
7 w
9
d
7 w
9
d
se arriviamo al caso 2 dal caso 1, allora x.p rosso, e quando RBDELETE-FIXUP viene invocato su di esso termina subito (arriva subito al
caso 0)
113
x 1
3
7 w
x 1
7 w
x 1
5 w
diventa il caso 4
9
d
7 w
9
d
d
V = {a, b, c, d, e}
E = {(b, a) (a, c) (b, c) (d, c) (e, d) (b, e)}
e
b
L'ordine dei vertici negli archi irrilevante
c
b
V = {a, b, c, d, e}
E = {(a, b) (a, d) (d, a) (b, e)
(c, e) (e, d), (e, e)}
115
c
e
b
V = {a, b, c, d, e}
E = {(a, b) (a, d) (d, a) (b, e)
(c, e) (e, d), (e, e)}
a
b
c
d
e
Liste ad.
d
b c
[ ]
0
0
0
1
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
1
1
0
1
Matrice ad.
116
d
c
a
b
c
d
e
b
e
e
a
e
V = {a, b, c, d, e}
E = {(a, b) (a, d) (d, a) (b, e)
(c, e) (e, d), (e, e)}
a
b
c
d
e
b c
d
Liste ad.
d
[ ]
0
0
0
1
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
1
1
0
1
Matrice ad.
117
118
119
c
e
a
a
b
V = {a, b, c, d, e}
E = {(b, a) (a, c) (b, c) (d, c) (e, d) (b, e)}c
d
e
[ ]
0
1
1
0
0
1
0
1
0
1
1
1
0
1
0
0
0
1
0
1
0
1
0
1
0
120
121
L'algortitmo in breve:
all'inizio tutti i nodi sono bianchi, tranne s (la
sorgente), che grigio
manteniamo i nodi di cui dobbiamo ancora visitare i
nodi adiacenti in una coda (che gestita con politica
FIFO!)
all'inizio la coda contiene solo s
a ogni iterazione del ciclo, eliminiamo dalla coda un
elemento u, e ne visitiamo i nodi adiacenti che sono
ancora bianchi (cio che devono essere ancora
visitati)
Si noti che, se u.dist la distanza del nodo u da
s, la distanza dei nodi bianchi adiacenti ad u
u.dist+1 (a meno che non mettiamo dei pesi agli
archi )
122
BFS: pseudocodice
BFS(G, s)
1 for each u G.V {s}
2
u.color := WHITE
3
u.dist :=
4
s.color := GRAY
5
s.dist := 0
6
Q :=
7
ENQUEUE(Q, s)
8
while Q
9
u := DEQUEUE(Q)
10
for each v u.Adj
11
if v.color = WHITE
12
v.color := GRAY
13
v.dist := u.dist +1
14
ENQUEUE(Q, v)
15
u.color := BLACK
123
124
125
DFS: considerazioni
Come in BFS, in DFS coloriamo i nodi di bianco, grigio
e nero (con lo stesso significato che in BFS)
Come detto in precedenza, questa volta usiamo una
politica di visita LIFO, quindi usiamo un meccanismo
analogo a quello dello stack
in questo caso, il meccanismo a stack viene dal fatto
che l'algoritmo ricorsivo
invece di fare push e pop di vertici su uno stack,
facciamo push e pop di chiamate ricorsive
dell'algoritmo sullo stack delle chiamate (vedere
corsi base informatica):
push = invochiamo l'algoritmo
ricorsivamente
pop = la chiamata ricorsiva termina
L'algoritmo DFS tiene traccia di quando i nodi sono
messi sullo stack ed anche di quando sono tolti da
esso
c' una variabile (globale), time, che messa a 0
nella fase di inizializzazione dell'algoritmo, e che
incrementata di 1 sia appena dopo un nodo messo
sullo stack che appena prima di togliere un nodo
dallo stack
usiamo la variabile time per tenere traccia di 2 altri
valori:
il tempo di quando inizia la scoperta
(discovery) di un nodo, ed il tempo di quando
la scoperta termina
l'inizio della scoperta di un nodo u
memorizzata nell'attributo u.d, mentre la sua fine
nell'attributo u.f
126
DFS: pseudocodice
DFS(G)
1 for each u G.V
2
u.color := WHITE
3 time := 0
4 for each u G.V
5
if u.color = WHITE
6
DFS-VISIT(u)
mutande
calze
orologio
pantaloni
scarpe
camicia
cintura
cravatta
giacca
128
begin
4
3
10
6
5
7
end
129
calze
mutande
pantaloni
scarpe
orologio camicia
cintura cravatta
giacca
130
131
in cui TOPSORT-VISIT :
TOPSORT-VISIT(L, u)
1 u.color := GRAY
2 for each v u.Adj
3
if v.color = WHITE
4
TOPSORT-VISIT(L, v)
5 crea l'elemento di lista x
6 x.key := u
7 LIST-INSERT(L, x)
8 u.color := BLACK
Argomenti Avanzati
133
134
135
10
prezzo pi
10
17
17
20
24
30
136
137
138
139
T j
Tempo di esecuzione: T(n) = 1 +
cio T(n) = 2n
j=0
lo si pu vedere sostituendo la soluzione
nella ricorrenza
140
Tempo di esecuzione: 2n
Il tempo di esecuzione cos alto perch gli
stessi problemi vengono risolti pi e pi volte:
141
142
143
144
10
pi
10
17
17
20
24
30
r[i]
10
13
17
18
22
25
30
s[i]
10
147
148
10
11
si
12
fi
10
11
12
13
14
150
151
Algoritmo goloso
E' inutile per provarle tutte per risolvere il problema
Sij, sufficiente prendere l'attivit a1 che finisce per
prima in Sij, e risolvere il problema Skj, con k prima
attivit in Sij che inizia dopo la fine di a1
se chiamiamo Sk l'insieme di tutte le attivit che
iniziano dopo la fine di ak, cio Sk = {at S : fk st},
dopo che abbiamo preso a1, ci rimane da risolvere il
solo problema S1
Abbiamo il seguente risultato:
dato un sottoproblema Sk, se am l'attivit che finisce
per prima in Sk, am inclusa in qualche sottoinsieme
massimo di attivit mutuamente compatibili di Sk
supponiamo che Ak sia un sottoinsieme massimo di
Sk, e chiamiamo aj l'attivit che finisce per prima in
Ak; allora o aj = am, oppure fm fj, e se sostituisco aj
con am in Ak ho ancora un sottoinsieme massimo A'k
di Sk
Quindi, per risolvere il problema di ottimizzazione mi
basta ogni volta scegliere l'attivit che finisce prima,
quindi ripetere l'operazione sulle operazioni che
iniziano dopo quella scelta
152
Pseudocodice (1)
Versione ricorsiva
s e f sono array con, rispettivamente, i
tempi di inizio e di fine delle attivit
k l'indice del sottoproblema Sk da
risolvere (cio l'indice dell'ultima attivit
scelta
n la dimensione (numero di attivit) del
problema originario
RECURSIVE-ACTIVITY-SELECTOR(s,f,k,n)
1 m := k + 1
2 while m n and s[m] < f[k]
3
m := m + 1
4 if m n
5 return {am}
RECURSIVE-ACTIVITY-SELECTOR(s,f,m,n)
6 else return
153
Pseudocodice (2)
Versione iterativa:
GREEDY-ACTIVITY-SELECTOR(s,f)
1 n := s.length
2 A := {a1}
3 k := 1
4 for m := 2 to n
5
if s[m] f[k]
6
A := A {am}
7
k := m
8 return A
154
155
156
157
Computazioni nondeterministiche
(richiami)
Data una macchina di Turing nondeterministica M,
definiamo la sua complessit temporale TM(x) per
riconoscere la stringa x come la lunghezza della
computazione pi breve tra tutte quelle che accettano x
TM(n) poi (nel caso pessimo) il massimo tra tutti i
TM(x) con
|x| = n
Quindi NTIME(T) la classe (l'insieme) dei linguaggi
(ricorsivi) riconoscibili in tempo T mediante macchine
di Turing nondeterministiche a k nastri di memoria
Tantissimi problemi si risolvono in modo molto naturale
mediante meccanismi nondeterministici (per esempio,
trovare un cammino in un grafo che tocca tutti i nodi)...
... per i meccanismi di computazione reali sono
deterministici
se riuscissimo a trovare una maniera poco onerosa per
passare da una formulazione nondeterminisitca ad una
deterministica, tantissimi problemi interessanti
potrebbero essere risolti (in pratica) in modo
(teoricamente) efficiente
per spesso abbiamo notato una esplosione nel
passaggio da un meccanismo ND ad uno D (quando
i 2 meccanismi sono equipotenti, come peraltro il
caso delle MT)
per esempio, esplosione del numero degli stati
nel passare da NDFSA a DFSA
158
159
LA domanda: P = NP?
boh...
probabilmente no, ma non si ancora
riusciti a dimostrarlo
Pi in generale:
LogSpace P NP PSPACE;
LogSpace PSPACE
Ma
Alcuni esempi di problemi della classe NP:
Soddisfacibilit di formule di logica
proposizionale (SAT): data una formula F
di logica proposizionale, esiste un
assegnamento dei valori alle lettere
proposizionali che compaiono in F tale che
F vera?
detto in altro modo: F ammette un
modello?
Circuito hamiltoniano (HC): dato un grafo
G, esiste un cammino in G tale che tutti i
nodi del grafo sono toccati una ed una sola
volta prima di tornare al nodo di partenza?
160
162
SAT NP-difficile
Come ridurre un generico problema NP a
SAT in tempo polinomiale
(deterministicamente)
Lidea base tutto sommato non
particoramente complicata (come tutte le
grandi idee ):
Fornire una computazione deterministica che,
per ogni MT non deterministica M con
complessit polinomiale p e per ogni stringa
x sullalfabeto di M, tale che |x| = n, produca
come uscita una formula proposizionale W, in
tempo polinomiale p(n), tale che W sia
soddisfacibile se e solo se x L(M).
163
165
166
Qt ,k
0t p ( n ) 0 k |Q|1
t , k1
0t p ( n )
0 k1 k 2 |Q|1
Qt ,k2
H t ,i
0t p ( n ) 0i p ( n )
0t p ( n )
0i , j p ( n )
i j
t ,i
H t , j
Ct ,i ,h
0t p ( n ) 0 h | A|1
0i p ( n )
0t p ( n )
0i p ( n )
0 h , k | A|1
hk
t ,i , h
Ct ,i ,k
n i p ( n )
C0,i,0
169
Qp ( n),i1 Qp ( n),iS
dove {qi1,...,qis} = F
AC ha (1) letterali.
170
0t p ( n )
0i p ( n )
0 k |Q|1
0 h | A|1
t ,k
...
0t p ( n )
0i p ( n )
0 k |Q|1
0 h| A|1
t ,k
t ,k
t ,k
172
0t p ( n )
0i p ( n )
0 k |Q|1
0 h | A|1
t ,i
Ct ,i ,h Ct 1,i ,h
H t ,i Ct ,i ,h Ct 1,i ,h'
173
174
Osservazioni conclusive
C0 CA, dove C0 la configurazione iniziale e CA
una configurazione di accettazione per M, se e solo
se W risulta soddisfacibile.
Una formula proposizionale che contiene n occorrenze
di variabili logiche si pu codificare come una
stringa di lunghezza (nlog n). Quindi, per ogni x
di lunghezza n, la sua traduzione (x) in una stringa
che codifica W certamente non pi lunga di
(p4(n)). Una volta compreso che la traduzione
pu essere eseguita da una MT deterministica
(anche multinastro) in un tempo (|(x)|), la
riducibilit in tempo polinomiale completamente
dimostrata.
Non bisogna effettivamente conoscere una MT M che
risolva il problema originale. Lesistenza di tale
macchina garantisce lesistenza di una macchina
riducente in tempo polinomiale. Tuttavia, se si
conosce una simile macchina M e se noto un
limite di tempo polinomiale p per la sua
computazione nondeterministica, allora la
dimostrazione consente effettivamente di costruire
la macchina che esegue la riduzione dei problemi.
Corollario
Il problema di stabilire la soddisfacibilit delle formule
proposizionali in forma normale congiuntiva NP 175
completo.
Intuizione ed esempio
Il procedimento consiste nel costruire G come
aggregazione di due classi di pezzi. I pezzi del
primo tipo saranno associati a ciascuna variabile
logica in W e, in un certo senso, ne mostreranno
tutte le occorrenze. I pezzi del secondo tipo saranno
associati a ciascuna clausola di W e legati ai nodi
che appartengono ai pezzi del primo tipo, in modo
tale che il loro attraversamento garantisca la verit
della clausola associata.
177
W: A1 ( A1 A2)
NB: L21 = A1
178
Pi in generale:
179
180
181
Riassumendo
I modelli
Il calcolo
Lanalisi e la sintesi di algoritmi
Inventati da capire e analizzare
Inventati da catalogare
Da inventare o soltanto da
Applicare/adattare
182
Riassumendo:
a voi la parola
(grazie per la pazienza)
183