Sei sulla pagina 1di 94

Algoritmi e Strutture Dati

Capitolo 9 - Grafi

Alberto Montresor
Università di Trento

This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License. To view a
copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative
Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.

© Alberto Montresor 1
Esempi di grafi

© Alberto Montresor 2
Problemi sui grafi

✦ Visite
✦ Visite in ampiezza (numero di Erdös)
✦ Visite in profondità (ordinamento topologico, componenti (fortemente) connesse)

✦ Cammini minimi
✦ Da singola sorgente
✦ Fra tutte le coppie di vertici

✦ Alberi di connessione minimi


✦ Problemi di flusso
✦ ....

© Alberto Montresor 3
Grafi orientati e non orientati: definizione

✦ Un grafo orientato G è una coppia ✦ Un grafo non orientato G è una


(V, E) dove: coppia (V, E) dove:
✦ Insieme finito dei vertici V ✦ Insieme finito dei vertici V
✦ Insieme degli archi E: relazione ✦ Insieme degli archi E:
binaria tra vertici coppie non ordinate

2 2
3 3
1 A
1

6 6
4 5 4 5

V = {1, 2, 3, 4, 5, 6 } V = {1, 2, 3, 4, 5, 6 }
E = { (1,2), (1,4), (2,3), (4,3), E = { [1,2], [1,4], [2,3], [3,4],
(5,3), (4,5), (4,1) }
© Alberto Montresor
[3,5], [4,5] } 4
Definizioni: incidenza e adiacenza

✦ In un grafo orientato
✦ un arco (u,v) si dice incidente da u in v

✦ In un grafo non orientato


✦ la relazione di adiacenza tra vertici è simmetrica

✦ Un vertice v si dice adiacente a u


se e solo se (u, v) ∈ E
(1,2) è incidente da 1 a 2
(1,4) è incidente da 1 a 4
(4,1) è incidente da 4 a 1
2
3 2 è adiacente ad 1
1 3 è adiacente a 2, 4, 5
1 è adiacente a 4 e viceversa
6 2 non è adiacente a 3,4
6 non è adiacente ad alcun vertice
4 5

© Alberto Montresor 5
Rappresentazione grafi

✦ Poniamo
✦ n = |V| numero di nodi
✦ m = |E| numero di archi

✦ Matrice di adiacenza
✦ Spazio richiesto O(n2)
✦ Verificare se il vertice u è adiacente a v richiede tempo O(1)
✦ Elencare tutti gli archi costa O(n2)

✦ Liste di adiacenza
✦ Spazio richiesto O(n+m)
✦ Verificare se il vertice u è adiacente a v richiede tempo O(n)
✦ Elencare tutti gli archi costa O(n+m)

© Alberto Montresor 6
diacenza M = [muv ] (detta anche matrice di adiacenza nodi-nodi)
Matrice di adiacenza: grafo orientato o non orientato
n ⇥ n, tale che:

1, se (u, v) ⌅ E, Spazio: n2
muv =
0, se (u, v) ⌅
/ E.
1 2 3 4 5 6
to, la matrice è simmetrica, ovvero muv = mvu per0 ogni 1 u,0v. Un
1 0 0
nza è mostrata nella Fig. 9.6.
0 0 1 0 0 0
e è banale verificare
2
in tempo O(1) se un dato arco è presente nel
il tempo per ricavare l’insieme di3 adiacenza G.adj0(u) 0di un 0 qual-
0 1 0
, indipendentemente
1 dalla cardinalità di G.adj(u), 1perché
0 occorre
1 0 0 0
matrice alla ricerca degli elementi muv = 1. Inoltre, lo spazio
6 02 0 0 1 0 0
n ) e il tempo per esaminare tutti gli archi è (n ), anche se il
2

ene un numero m 4 di archi che è O(n).


5 Se il grafo è pesato, allora
o i pesi degli archi al posto degli elementi binari. Se puv è il peso
trice M diventa:
© Alberto Montresor 7
Lista di adiacenza: grafo orientato

G.adj(u) = { v | (u,v) ∈ E } Spazio: a⋅n + b⋅m

a b

1 2 4 nil

2 3 nil
2
3 3 4 nil

1 4 1 5 nil

5 3 nil
6
6 nil
4 5

© Alberto Montresor 8
⇧i ⌅ [j + 1, ultimo] : A[i] ⇤ A[j]
Matrice di adiacenza: grafo non orientato


1, se [u, v] ⌅ E, Spazio: n2 o n(n+1)/2
muv =
0, se [u, v] ⌅
/ E.
1 2 3 4 5 6

1 0 1 0 1 0 0
2 2 1 0 1 0 0 0
3
A
1 3 0 1 0 1 1 0

4 1 0 1 0 1 0
5 6
5 0 0 1 1 0 0
4 5
6 0 0 0 0 0 0

© Alberto Montresor 9
Liste di adiacenza: grafo non orientato

G.adj(u) = { v | [u,v] ∈ E or [v,u] ∈ E} Spazio: a⋅n + 2⋅b⋅m


a b

a b

1 2 4 nil
2
2 1 3 nil
3
A
1 3 2 4 5 nil

4 1 3 5 nil
6
5 3 4 nil
4 5 6 nil

© Alberto Montresor 10
zzazione è banale verificare in tempo O(1) se un dato arco è presente nel
rtroppo,
Grafi pesati per ricavare l’insieme di adiacenza G.adj(u) di un qual-
il tempo
re (n), indipendentemente dalla cardinalità di G.adj(u), perché occorre
ga della
✦ In matrice alla ogni
alcuni casi ricerca degli
arco elementi
ha un muv =guadagno)
peso (costo, 1. Inoltre,associato
lo spazio
o è (n✦ 2 ) e il tempo per esaminare tutti gli archi è (n2 ), anche se il
Il peso può essere determinato tramite una funzione di costo
ro contiene un numero m di archi che è O(n). Se il grafo è pesato, allora
p: V ×V → R, dove R è l’insieme dei numeri reali
utilizzano i pesi degli archi al posto degli elementi binari. Se puv è il peso
✦ Quando tra due vertici non esiste un arco, il peso è infinito
ra la matrice M diventa:

puv , se (u, v) ⌅ E, 1 2 3 4 5 6
muv =
+⇤ (oppure ⇤) se (u, v) ⌅
/ E. * 3 * 1 * *
1
2 3 * 4 * * *
4 * 4 * 4 7 *
3 2 3
3
4 1 * 4 * 8 *
1
4 5 * * 7 8 * *
1 7 6

8 6 * * * * * *
4 5
© Alberto Montresor 11
prevedono la possibilità di inserire o cancellare nodi o archi, di ottenere l’insieme di tutti i nodi
Specifica
o dei nodi adiacenti ad un particolare nodo.
Infine, la situazione può essere ulteriormente complicata dal fatto che in molte applica
ulteriori
G RAPH
informazioni (dette etichette, o pesi) sono associate ai nodi e/o agli archi. In tal
si parla di grafi etichettati (o pesati), i quali richiedono ulteriori operatori, per reperi
Graph( ) % Crea un grafo vuoto
modificare le informazioni associate a nodi e/o archi. Non è quindi un caso che nelle li
insertNode(N ODE u) % Aggiunge il nodo u al grafo
dei più diffusi linguaggi di programmazione siano disponibili numerose realizzazioni
insertEdge(N ODE u, N ODE v) % Aggiunge l’arco (u, v) al grafo
strutture di dati insieme, dizionario, etc., ma che non vi sia una realizzazione standard
(N ODE u) % Rimuove il nodo u dal grafo
grafi. deleteNode
Non staremo quindi noi ad inventarne una fittizia.
deleteEdge
Nelle (N ODEsezioni
prossime u, N ODEdiscuteremo
v) % Rimuove
dei principali l’arco (u,
approcci per nel grafo
v) realizzare la struttu
S ET adj
dati grafo. (N ODE u) dall’ipotesi semplificativa
Partiremo % Restituisce
chel’insieme dei nodi
l’insieme adiacenti
statico degliadnunodi possa e
S ET V() dai numeri da 1 a n. Per eseguire%un’operazione
rappresentato su tutti
Restituisce l’insieme i nodi
di tutti i nodie su tutti gli
utilizzeremo il costrutto foreach come segue:

Di tutte u
foreach queste
⌅ G.procedure,
V() do discuteremo la realizzazione delle sole procedure adj() e V();
✦ Complessità

il motivo principale è che, in questo testo, i grafi sono usati ✦per rappresentare relazioni tra
foreach v ⌅ G.adj(u) do O(n+m) liste di adiacenza
oggetti che non mutano nel tempo. In altri termini, non si effettuano mai né cancellazioni né
inserzioni, fai
né diun’operazione
nodi né di archi, sull’arco (u, v)
ma si esplorano sistematicamente grafi
2 forniti in ingresso, al
✦ O(n ) matrice di adiacenza
fine di determinarne sottoinsiemi di nodi e/o di archi che soddisfino certe proprietà.
Qualora il grafo sia dinamico, la struttura di dati più adatta O(m) “operazioni”

per rappresentarlo dipende
Il primo
dalla foreach
particolare
© Alberto Montresor
scorre del
utilizzazione tutti i nodi
grafo delRealizzazioni
stesso. grafo, mentre il secondo
efficienti scorre
di insiemi tutti i
e dizionari nodi 12adi
Definizioni: Grado

✦ In un grafo non orientato


✦ il grado di un vertice è il numero di archi che partono da esso

✦ In un grafo orientato
✦ il grado entrante (uscente) di un vertice è il numero di archi incidenti in (da) esso
in : 1 in : 2
in : 2 out: 1 out: 1
2 2
2 3 out: 1 3
A
1 3 1
2
in : 1
out: 1
4 5 2 4 5
3 in : 2
out: 2 in : 0
6 6
0 out: 0
© Alberto Montresor 13
Definizioni: Cammini

✦ In un grafo orientato G=(V,E)


✦ un cammino di lunghezza k è una sequenza di vertici u0, u1, ..., uk tale che
(ui, ui+1) ∈ E per 0 ≤ i ≤ k–1

✦ In un grafo non orientato G=(V,E)


✦ una catena di lunghezza k è una sequenza di vertici u0, u1, ..., uk tale che
[ui, ui+1] ∈ E per 0 ≤ i ≤ k–1

2 Esempio: 1, 2, 3, 5, 4 è una
3 catena nel grafo con lunghezza 4
1
Un cammino (catena) si dice semplice
se tutti i suoi vertici sono distinti
(compaiono una sola volta nella
4 sequenza)
5

© Alberto Montresor 14
Definizioni: Cicli

✦ In un grafo orientato G=(V,E)


✦ un ciclo di lunghezza k è un cammino u0, u1, ..., uk tale che
(ui, ui+1) ∈ E per 0 ≤ i ≤ k–1, u0 = uk, e k>2

✦ In un grafo non orientato G=(V,E)


✦ un circuito di lunghezza k è una catena u0, u1, ..., uk tale che
[ui, ui+1] ∈ E per 0 ≤ i ≤ k–1, u0 = uk, e k>2

Esempio: 1, 2, 3, 5, 4, 1 è un
2 circuito con lunghezza 5
3
1 Un ciclo (circuito) si dice semplice se
tutti i suoi vertici sono distinti
(tranne ovviamente l’ultimo)

4 5

© Alberto Montresor 15
Definizioni: Grafi aciclici

✦ Un grafo senza cicli è detto aciclico

2
3 Un grafo orientato aciclico
è chiamato DAG (Directed
1
Questo grafo è Acyclic Graph)
aciclico 6
2
4 5 3
1
2 6
3
1 4
Questo grafo non è 5
aciclico 6

4 5

© Alberto Montresor 16
Definizioni: Grafo completo

✦ Un grafo completo è un grafo che ha un arco tra ogni coppia di vertici.

Questo grafo è completo

2
Questo grafo non è completo 3
1

2
3
1 4 5

n
X1
4 n(n 1)
5 m= i=
i=1
2

© Alberto Montresor 17
Definizioni: Alberi

✦ Un albero libero è un grafo non orientato connesso, aciclico


✦ Se qualche vertice è detto radice, otteniamo un albero radicato
✦ Un insieme di alberi è detta foresta
rad
ice

2 2
3 3
1 1
6 6

4 5 4 5

8 9
© Alberto Montresor 18
Definizioni: Alberi di copertura

✦ In un grafo non orientato G=(V, E)


✦ un albero di copertura T è un albero libero T = (V, E′) composto da tutti i nodi di
V e da un sottoinsieme degli archi (E′ ⊆ E), tale per cui tutte le coppie di nodi del
grafo sono connesse da una sola catena nell’albero.

2
3
1
6

4 5

© Alberto Montresor 19
Un esempio di utilizzo dei grafi

✦ Sherlock Holmes indaga sulla morte del duca McPollock, ucciso da un’esplosione nel suo maniero:
✦ Watson: “Ci sono novità, Holmes: pare che il testamento, andato distrutto nell’esplosione, fosse stato
favorevole ad una delle sette ‘amiche’ del duca”
✦ Holmes: “Ciò che è più strano, è che la bomba sia stata fabbricata appositamente per essere nascosta
nell’armatura della camera da letto, il che fa supporre che l’assassino abbia necessariamente fatto
più di una visita al castello”
✦ Watson: “Ho interrogato personalmente le sette donne, ma ciascuna ha giurato di essere stata nel
castello una sola volta nella sua vita.
✦ (1) Ann ha incontrato Betty, Charlotte, Felicia e Georgia;
✦ (2) Betty ha incontrato Ann, Charlotte, Edith, Felicia e Helen;
✦ (3) Charlotte ha incontrato Ann, Betty e Edith;
✦ (4) Edith ha incontrato Betty, Charlotte, Felicia;
✦ (5) Felicia ha incontrato Ann, Betty, Edith, Helen;
✦ (6) Georgia ha incontrato Ann e Helen;
✦ (7) Helen ha incontrato Betty, Felicia e Georgia.
Vedete, Holmes, che le testimonianze concordano. Ma chi sarà l’assassino?”
✦ Holmes: “Elementare, Watson: ciò che mi avete detto individua inequivocabilmente l’assassino!”
© Alberto Montresor 20
Un esempio di9.utilizzo
CAPITOLO GRAFI dei grafi 149

F E

H B C

G A

a)

A H B
H B
G B
?
G A ? G A
H

b)

B
H E

G F C

c)

Figura
© Alberto Montresor 9.5: a) Il grafo degli incontri. b) Rappresentazione di circuiti di lunghezza 4 come intersezione di 21
Problema: Attraversamento grafi

✦ Definizione del problema


✦ Dato un grafo G=(V, E) ed un vertice r di V (detto sorgente o radice),
visitare ogni vertice raggiungibile nel grafo dal vertice r
✦ Ogni nodo deve essere visitato una volta sola

✦ Visita in ampiezza (breadth-first search)


✦ Visita i nodi “espandendo” la frontiera fra nodi scoperti / da scoprire
✦ Esempi: Cammini più brevi da singola sorgente

✦ Visita in profondità (depth-first search)


✦ Visita i nodi andando il “più lontano possibile” nel grafo
✦ Esempi: Componenti fortemente connesse, ordinamento topologico

© Alberto Montresor 22
Visita: attenzione alle soluzioni “facili”

✦ Prendere ispirazione dalla visita degli alberi


✦ Ad esempio:
6 utilizziamo una visita BFS basata su coda Algoritmi e Strut

✦ trattiamo i “vertici adiacenti” come se fossero i “figli”

visita(G RAPH G, N ODE r)


Q UEUE S Queue()
S.enqueue(r)
while not S.isEmpty() do
N ODE u S.dequeue()
{ esamina il nodo u }
foreach v ⇥ G.adj(u) do
S.enqueue(v)

© Alberto Montresor 23
Visita: attenzione alle soluzioni “facili”

✦ Prendere ispirazione dalla visita degli alberi


✦ Ad esempio:
6 utilizziamo una visita BFS basata su coda Algoritmi e Strut

✦ trattiamo i “vertici adiacenti” come se fossero i “figli”

visita(G RAPH G, N ODE r)


Q UEUE S Queue()
S.enqueue(r)
to

while not S.isEmpty() do


lia

N ODE u S.dequeue()
ag

{ esamina il nodo u }
Sb

foreach v ⇥ G.adj(u) do
S.enqueue(v)

© Alberto Montresor 23
Esempio di visita - errata

r
6

2
3 9
1
11

4 5 10
8

Coda:{6}

© Alberto Montresor 24
Esempio di visita - errata

r u = 6
6

2
3 9
1
11

4 5 10
8

Coda:{2,4,9}

© Alberto Montresor 25
Esempio di visita - errata

r u = 2
6

2
3 9
1
11

4 5 10
8

Coda:{4,9,3,1,6}

© Alberto Montresor 26
Esempio di visita - errata
r u = 4
6

2
3 9
1
11

4 5 10
8

7  Nodi già visitati:


 “Marcare” un nodo visitato in modo che
Coda:{9,3,1,6,5,6,1,3} non possa essere visitato di nuovo
 Bit di marcatura: nel vertice, array
separato, etc.

© Alberto Montresor 27
Algoritmo generico per
Genericamente, la visita
la visita di un grafo può essere descritta dalla procedura visita

visita(G RAPH G, N ODE r)  S è l'insieme frontiera


S ET S Set()  Il funzionamento di
S.insert(r) insert() e remove()
{ marca il nodo r come “scoperto” } non è specificato
while S.size() > 0 do
N ODE u S.remove()
{ esamina il nodo u }
foreach v ⇥ G.adj(u) do
{ esamina l’arco (u, v) }
if v non è già stato scoperto then
{ marca il nodo v come “scoperto” }
S.insert(v)

L’insieme S contiene i nodi che sono stati scoperti, ma non ancora visitati. 28I n
© Alberto Montresor
Visita in ampiezza (breadth first search, BFS)

Cosa vogliamo fare?


✦ Visitare i nodi a distanze crescenti dalla sorgente
✦ visitare i nodi a distanza k prima di visitare i nodi a distanza k+1

✦ Generare un albero BF (breadth-first)


✦ albero contenente tutti i vertici raggiungibili da r e tale che il cammino da r
ad un nodo nell'albero corrisponde al cammino più breve nel grafo

✦ Calcolare la distanza minima da s a tutti i vertici raggiungibili


✦ numero di archi attraversati per andare da r ad un vertice

© Alberto Montresor 29
booleani; la posizione corrispondente al nodo u viene inizializzata a false, per div
Visita
quando in ilampiezza (breadth
nodo viene first search,
“scoperto” BFS)
durante la visita.
 Insieme S gestito
bfs(G RAPH G, N ODE r) tramite una coda
Q UEUE S ⇥ Queue( )
 visitato[v] corrisponde
S.enqueue(r) alla marcatura del nodo v
boolean[ ] visitato ⇥ new boolean[1 . . . G.n]
foreach u ⇤ G.V() {r} do visitato[u] ⇥ false
visitato[r] ⇥ true
while not S.isEmpty() do
N ODE u ⇥ S.dequeue()
{ esamina il nodo u }
foreach v ⇤ G.adj(u) do
{ esamina l’arco (u, v) }
if not visitato[v] then
visitato[v] ⇥ true
S.enqueue(v)

© Alberto Montresor 30
Applicazione BFS: Numero di Erdös

✦ Paul Erdös (1913-1996)


✦ Matematico
✦ Più di 1500 articoli, con più di 500 co-autori

✦ Numero di Erdös
✦ Erdös ha erdös = 0
✦ I co-autori di Erdös hanno erdös = 1
✦ Se X ha scritto una pubblicazione scientifica con
un co-autore con erdös = k, ma non con
un co-autore con erdös < k, X ha erdös=k +1
✦ Chi non è raggiunto da questa definizione ha erdös = +∞

✦ Vediamo un’applicazione di BFS per calcolare il numero di Erdös

© Alberto Montresor 31
uniti da un arco se e solo se hanno scritto un articolo scientifico insieme), è possibile calcolare
ilCalcolo
numero didelErdős
numero di Erdös
di ogni autore utilizzando una visita in ampiezza a partire da Erdős.

erdos(G RAPH G, N ODE r, integer[ ] erdős, N ODE[ ] p)


Q UEUE S ⇥ Queue()
S.enqueue(r)
foreach u ⌅ G.V() {r} do erdős[u] = ⇤
erdős[r] ⇥ 0
p[r] ⇥ nil
while not S.isEmpty() do
N ODE u ⇥ S.dequeue()
foreach v ⌅ G.adj(u) do % Esamina l’arco (u, v)
if erdős[v] = ⇤ then % Il nodo u non è già stato scoperto
erdős[v] ⇥ erdős[u] + 1
p[v] ⇥ u
S.enqueue(v)

In questa versione della BFS, invece di utilizzare un vettore di booleani per la marcatura,
utilizziamo il vettore erdős di interi, in cui i nodi non ancora scoperti hanno numero di Erdős
pari a ⇤. Il nodo radice (Erdős) viene inizializzato a zero; ogni volta che viene analizzato un32
© Alberto Montresor
Esempio
0
6

2
3 10
1
11

4 5 12
9

Coda:{6}

© Alberto Montresor 33
Esempio
0
1 6
1
2
3 9
1
11
1

4 5
8 10

Coda:{2,4,9}

© Alberto Montresor 34
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5
8 10

Coda:{4,9,3,1}

© Alberto Montresor 35
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8

Coda:{9,3,1,5}

© Alberto Montresor 36
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8
2

Coda:{3,1,5,8}

© Alberto Montresor 37
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8
2

Coda:{1,5,8}

© Alberto Montresor 38
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8
2

Coda:{5,8}

© Alberto Montresor 39
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 2
5 10
8
2
3
7

Coda:{8,7}

© Alberto Montresor 40
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8
2
3
7

Coda:{7}

© Alberto Montresor 41
Esempio
0
1 6
1
2
3 2 9
1 2
11
1

4 5 2 10
8
2
3
7

Coda:{}

© Alberto Montresor 42
u sono tutti i nodi v scoperti durante la visitap[r] di nil (u, v). Il cammino nell’albero c
⇥archi
Albero dei cammini BFS
il nodo r ad un nodo s è ovviamente un cammino while notanche nel grafo,
S.isEmpty () doed ha lunghezza e
La procedura erdos() memorizza l’alberoNdei ODE cammini
u ⇥ S.BFS
✦ La visita BFS può essere utilizzata per ottenere il cammino più breve fra due
in un ()
dequeue vettore di padr
p[v] contiene
vertici (numero se archi)
u di v è stato scoperto durante foreach
la visita adj(u)
v ⌅delG.nodo u do
e in particolare
(u, v). Il vettore p è passato in input alla procedura
✦ Albero di copertura di G radicato in r
erdos(),
if erdős[v] =⇤ conthen
ogni elemento d
inizializzato a nil. erdős[v] ⇥ erdős[u] + 1
✦ Memorizzato tramite vettore dei padri p
Una volta ottenuto il vettore p, è possibile ottenere p[v]
il cammino
⇥u dal nodo radice r
nerico nodo
✦ Figli di us- tramite la procedura
nodi v tali che (u,v) ∈stampaCammino
E . S.
Sienqueue (v)
noti l’interessante uso della ri
che epermette di stampare
v non è ancora visitatogli autori nell’ordine atteso da r ad s.

stampaCammino(G RAPH G, N ODE r, N ODE s, N ODE[ ] p)


In questa versione della BFS, invece di ut
if r = s then print s
utilizziamo il vettore erdős di interi, in cui i n
else if p[s] = nil then
print “nessun cammino da r pari
a s” a ⇤. Il nodo radice (Erdős) viene inizial
else
arco (u, v) e il nodo v non è ancora stato scop
stampaCammino(G, r, p[s], p)
print s

© Alberto Montresor 43
Algoritmo generico per la visita

✦ Alcune definizioni ✦ Alcune cose da notare:


✦ L'albero T contiene i vertici ✦ I nodi vengono visitati al più una
visitati volta (marcatura)
✦ S ⊆ T contiene i vertici aperti: ✦ Tutti i nodi raggiungibili da r
vertici i cui archi uscenti non sono vengono visitati
ancora stati percorsi ✦ Ne segue che T contiene
✦ T-S ⊆ T contiene i vertici chiusi: esattamente tutti i nodi
vertici i cui archi uscenti sono stati raggiungibili da r
tutti percorsi
✦ V-T contiene i vertici non visitati
✦ Se u si trova lungo il cammino che
va da r al nodo v, diciamo che:
✦ u è un antenato di v
✦ v è un discendente di u
© Alberto Montresor 44
Visita in profondità (depth first search, DFS)

✦ Visita in profondità ✦ Struttura di dati


158
✦ E' spesso una “subroutine” della ✦ Ricorsione al posto di una pila
soluzione di altri problemi esplicita
zato a tutti false). Come negli alberi, è possibile d
✦ Utilizzata per coprire l'intero grafo, postvisita (riga (2)).
non solo i nodi raggiungibili da
una singola sorgente dfs(G RAPH G, N ODE u, boolean[ ] visitato)
(diversamente da BFS) visitato[u] true
(1) { esamina il nodo u (caso previsita) }
✦ Output foreach v ⇥ G.adj(u) do
✦ Invece di un albero, una foresta DF { esamina l’arco (u, v)
(depth-first) Gπ=(V, Eπ) if not visitato[v] then
dfs(G, v, visitato)
✦ Contenente un insieme di
(2) { esamina il nodo u (caso postvisita) }
alberi DF

Esempio 9.14 (DFS). L’ordine di visita DFS dei n


mendo di partire dal nodo r = 1 e che gli insiemi
45
a
© Alberto Montresor
Componenti fortemente connesse

 Terminologia
 Componenti connesse (connected components, CC)
 Componenti fortemente connesse (strongly connected components, SCC)

 Motivazioni
 Molti algoritmi che operano sui grafi iniziano decomponendo
il grafo nelle sue componenti
 L'algoritmo viene poi eseguito su ognuna delle componenti
 I risultati vengono poi ricomposti assieme

© Alberto Montresor 46
Definizioni: Raggiungibilità

✦ In grafo orientato (non orientato)


✦ Se esiste un cammino (catena) c tra i vertici u e v, si dice che v è raggiungibile da
u tramite c

1 è raggiungibile da 4 e 4 è raggiungibile da 1 ma non


viceversa viceversa

2 2
3 3
1 1

4 5 4 5

© Alberto Montresor 47
Definizioni: Grafi connessi e componenti connesse

✦ In un grafo non orientato G


✦ G è connesso ⇔ esiste un cammino da ogni vertice ad ogni altro vertice
✦ Un grafo G′ = (V′, E′) è una componente connessa di G ⇔
è un sottografo di G fortemente e massimale

✦ Definizioni
✦ G′ è un sottografo di G (G′ ⊆ G) se
e solo se V′ ⊆ V e E′ ⊆ E 2
✦ G′ è massimale ⇔ non esiste un 3
A
1
sottografo G′′ di G che sia
connesso e “più grande” di G′,
ovvero tale per cui G′ ⊆ G′′ ⊆ G
4 5

6 48
© Alberto Montresor
Applicazioni DFS: Componente connesse

✦ Problema
✦ Verificare se un grafo non orientato è connesso
✦ Identificare le componenti connesse di cui è composto

✦ Soluzione
✦ Un grafo è connesso se, al termine della DFS, tutti i nodi sono stati marcati
✦ Altrimenti, una singola passata non è sufficiente e la visita deve ripartire da
un nodo non marcato, scoprendo una nuova porzione del grafo

✦ Strutture dati
✦ Vettore id degli identificatori di componente
✦ id[u] è l’identificatore della componente connessa a cui appartiene u

© Alberto Montresor 49
CAPITOLO 9. GRAFI 15
Componenti connesse

integer[ ] cc(G RAPH G, N ODE[ ] ordine)


integer[ ] id new integer[1 . . . G.n]
foreach u ⇥ G.V() do id[u] 0
integer conta 0
for integer i 1 to G.n do
if id[ordine[i]] = 0 then
conta conta + 1
ccdfs(G, conta, ordine[i], id)
return id
ccdfs(G RAPH G, integer conta, N ODE u, integer[ ] id)
id[u] conta
foreach v ⇥ G.adj(u) do
if id[v] = 0 then
ccdfs(G, conta, v, id)

© Alberto Montresor 50
Componenti connesse

2
5 6
1
10

4 7
9 11

© Alberto Montresor 51
Componenti connesse

2
1 5 6
1
10

4 7
9 11

© Alberto Montresor 51
Componenti connesse

1 3

2
1 5 6
1
10

4 7
9 11

© Alberto Montresor 51
Componenti connesse
1

1 3

2
1 5 6
1
10

4 7
9 11

© Alberto Montresor 51
Componenti connesse
1

1 3

2
1 5 6
1
10

4 7
9 11
1

© Alberto Montresor 51
Componenti connesse
1

1 3
2
2
1 5 6
1
10

4 7
9 11
1

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1
10

4 7
9 11
1

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1
10

4 2
7 11
9
1

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1
10

2
4 2
7 11
9
1

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1
10

2
4 2
7 11
9
1
2
8

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1 3
10

2
4 2
7 11
9
1
2
8

© Alberto Montresor 51
Componenti connesse
1

1 3
2 2
2
1 5 6
1 3
10

2 3
4 2
7 11
9
1
2
8

© Alberto Montresor 51
Alberi di copertura DFS

✦ La visita DFS genera l’albero (foresta) dei cammini DFS


✦ Tutte le volte che viene incontrato un arco che connette un nodo marcato ad uno
non marcato, esso viene inserito nell’albero T

✦ Gli archi non inclusi in T possono essere divisi 1 2


in tre categorie durante la visita:
✦ se l’arco è esaminato passando da un nodo di T
ad un altro nodo che è suo antenato in T , è 3 4
detto arco all’indietro
1
✦ se l’arco è esaminato passando da un nodo
di T ad un suo discendente (che non sia figlio)
in T è detto arco in avanti 2 4
✦ altrimenti, è detto arco di attraversamento
3

© Alberto Montresor 52
CAPITOLO 9. GRAFI
Schema DFS

✦ Variabili globali dfs-schema(G RAPH G, N ODE u)


✦ time orologio esamina il nodo u prima (caso pre-visita)
time time + 1; dt[u] time
✦ dt discovery time
foreach v ⇥ G.adj(u) do
✦ ft finish time esamina l’arco (u, v) di qualsiasi tipo
if dt[v] = 0 then
esamina l’arco (u, v) in T
dfs-schema(g, v)
else if dt[u] > dt[v] and ft[v] = 0 then
esamina l’arco (u, v) all’indietro
else if dt[u] < dt[v] and ft[v] ⇤= 0 then
esamina l’arco (u, v) in avanti
else
esamina l’arco (u, v) di attraversamento
esamina il nodo u dopo (caso post-visita)
time time + 1; ft[u] time
© Alberto Montresor 53
CAPITOLO 9. GRAFI
Esempio
[1, ]
dfs-schema(G RAPH G, N ODE u)
F
esamina il nodo u prima (caso pre-visita)
B time time + 1; dt[u] time
C foreach v ⇥ G.adj(u) do
I esamina l’arco (u, v) di qualsiasi tipo
A if dt[v] = 0 then
esamina l’arcoM (u, v) in T
dfs-schema(g, v)
else if dt[u] > dt[v] and ft[v] = 0 then
D esamina l’arco (u, v) all’indietro
E if dt[u] < dt[v]Land ft[v] ⇤= 0 then
Helseesamina l’arco (u, v) in avanti
else
esamina l’arco (u, v) di attraversamento
G
esamina il nodo u dopo (caso post-visita)
time time + 1; ft[u] time

è un antenato di u, allora esiste un cammino da v a u e u


esiste un ciclo, sia v il primo nodo del ciclo che viene vis
© Alberto Montresor
cammino che connette v ad u verrà prima o poi visitato,
54 e
Esempio
[1, ]
F
[2, ]
B
C I
A
M

D E L
H

© Alberto Montresor 55
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A
M

D E L
H

© Alberto Montresor 56
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A
M

[4, ] D E L
H

© Alberto Montresor 57
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A
M

[5, ]
[4, ] D E L
H

© Alberto Montresor 58
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A [6, ]
M
[5, ]
[4, ] D E L
H

© Alberto Montresor 59
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A [6, 7]
M
[5, ]
[4, ] D E L
H

© Alberto Montresor 60
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A [6, 7]
M
[5, ]
[4, ] D E L
H

G
[8, ]

© Alberto Montresor 61
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A [6, 7]
M
[5, ]
[4, ] D E
H L

[9, ]
G
[8, ]

© Alberto Montresor 62
Esempio
[1, ]
F
[2, ]
[3, ] B
C I
A [6, 7] [10, ]
M
[5, ]
[4, ] D E
H L

[9, ]
G
[8, ]

© Alberto Montresor 63
Esempio
[1, 18]
F
[2, 17]
[3, 16] B
C I
A [6, 7] [10, 11]
M
[5,14 ]
[4,15] D E
H L

[9, 12]
G
[8, 13]

© Alberto Montresor 64
Esempio
[1, 18]
F
[2, 17]
[3, 16] B
C I
A [6, 7] [10, 11]
[19, ]
M
[5,14 ]
[4,15] D E
H L

[9, 12]
G
[8, 13]

© Alberto Montresor 65
Esempio
[1, 18]
F
[2, 17]
[3, 16] B
C I
A [6, 7] [10, 11]
[19, ]
M
[5,14 ]
[20, ]
[4,15] D E
H L

[9, 12]
G
[8, 13]

© Alberto Montresor 66
Esempio
[1, 18]
F
[2, 17]
[3, 16] B
C I
A [6, 7] [10, 11]
[19, 22]
M
[5,14 ]
[20, 21]
[4,15] D E
H L

[9, 12]
G
[8, 13]

© Alberto Montresor 67
CAPITOLO 9. GRAFI
Classificazione degli archi

dfs-schema(G RAPH G, N ODE u)


esamina il nodo u prima (caso pre-visita)
time time + 1; dt[u] time [6, 7]
foreach v ⇥ G.adj(u) do D
esamina l’arco (u, v) di qualsiasi tipo
if dt[v] = 0 then [1, 8]
esamina l’arco (u, v) in T
dfs-schema(g, v) A B
[2, 5]
else if dt[u] > dt[v] and ft[v] = 0 then
esamina l’arco (u, v) all’indietro
else if dt[u] < dt[v] and ft[v] ⇤= 0 then
E C
esamina l’arco (u, v) in avanti
else [9, 10] [3, 4]
esamina l’arco (u, v) di attraversamento
esamina il nodo u dopo (caso post-visita)
time time + 1; ft[u] time
© Alberto Montresor 68
esamina il nodo u dopo (caso post-visita)
time time + 1; ft[u] time
Classificazione degli archi

 Cosa serve le classificazione?


è un antenato di u, allora esiste un cammino da v a u e un arco da u a v, ovvero un ciclo. Se
E' possibile
esiste un ciclo, dimostrare
sia v il primo nodo delalcune proprietà,
ciclo che che e sia (u, v) un arco del ciclo. Il
viene visitato
cammino chepoi connette
possonov essere sfruttate
ad u verrà prima onegli algoritmi
poi visitato, e da u verrà scoperto l’arco all’indietro
(u, v). Adattando opportunamente lo schema di cui sopra, si ottiene l’algoritmo ciclico() che
restituisce
 Esempio:true se il grafo contiene un ciclo. Si noti che non appena viene trovato un arco
all’indietro, la procedura termina restituendo true e chiudendo tutte le chiamate ricorsive.
 DAG non hanno archi all'indietro (dimostrare)

boolean ciclico(G RAPH G, N ODE u)


time time + 1; dt[u] time
foreach v ⇥ G.adj(u) do
if dt[v] = 0 then
if ciclico(G, v) then return true
else if dt[u] > dt[v] and ft[v] = 0 then
return true
time time + 1; ft[u] time
return false;

© Alberto Montresor 69
Ordinamento topologico

✦ Dato un DAG G (direct acyclic graph), un ordinamento topologico su G è un


ordinamento lineare dei suoi vertici tale per cui:
✦ se G contiene l’arco (u,v), allora u compare prima di v nell’ordinamento
✦ Per transitività, ne consegue che se v è raggiungibile da u,
allora u compare prima di v nell'ordinamento
✦ Nota: possono esserci più ordinamenti topologici

2
1 3 5 2 4

1 3 4

5
1 2 3 4 5

© Alberto Montresor 70
Ordinamento topologico

✦ Problema:
✦ Fornire un algoritmo che dato un grafo orientato aciclico, ritorni un ordinamento
topologico

✦ Soluzioni
Trovare ogni vertice che non ha alcun arco
✦ Diretta incidente in ingresso
Stampare questo vertice e rimuoverlo,
insieme ai suoi archi
Ripetere la procedura finché tutti i vertici
risultano rimossi
✦ Basata su DFS
Esercizio: scrivere lo pseudocodice per
questo algoritmo
Qual è la complessità?
 con matrici di adiacenza

 con liste di adiacenza

© Alberto Montresor 71
Soluzione diretta

2 2 2

1 3 4 3 4 4

5 5 5

Output: Output: 1 Output: 1 3

4 4

Output: 1 3 5 Output: 1 3 5 2 Output: 1 3 5 2 4


© Alberto Montresor 72
Ordinamento topologico basato su DFS

✦ Algoritmo [2,5]
✦ Si effettua una DFS 2
✦ L’operazione di visita consiste
[1, 10] [6,9] [3,4]
nell’aggiungere il vertice alla
testa di una lista“at finish time” 1 3 4
✦ Restituire la lista di vertici
[7,8]
✦ Output 5
✦ Sequenza ordinata di vertici,
in ordine inverso di finish time

✦ Perché funziona?

© Alberto Montresor 73
CAPITOLO 9. GRAFI 165
Ordinamento topologico basato su DFS

topSort(G RAPH G, integer [ ] ordine)


boolean[ ] visitato ⇥ boolean[1 . . . G.n]
foreach u ⇤ G.V() do visitato[u] ⇥ false
integer i ⇥ 1
foreach u ⇤ G.V() do
if not visitato[u] then
i ⇥ ts-dfs(G, u, i, visitato, ordine)

integer ts-dfs(G RAPH G, N ODE u, integer i, boolean[ ] visitato, N ODE[ ] ordine)


visitato[u] ⇥ true
foreach v ⇤ G.adj(u) do
if not visitato[v] then
i ⇥ ts-dfs(G, u, i, visitato, ordine)
ordine[G.n i + 1] ⇥ u
return i + 1

Esercizio 9.2 (DFS iterativa). Scrivere una versione iterativa della DFS basata su pila dove
© Alberto Montresor 74
Definizioni: Grafi fortemente connessi e componenti fortemente connesse

✦ In un grafo orientato G
✦ G è fortemente connesso ⇔ esiste un cammino da ogni vertice ad ogni altro
vertice
✦ Un grafo G′ = (V′, E′) è una componente fortemente connessa di G ⇔
è un sottografo di G fortemente connesso e massimale

✦ Definizioni
✦ G′ è un sottografo di G (G′ ⊆ G) se
2
e solo se V′ ⊆ V e E′ ⊆ E 3
✦ G′ è massimale ⇔ non esiste un 1
sottografo G′′ di G che sia 6
fortemente connesso e “più grande”
di G′, ovvero tale per cui G′ ⊆ G′′ ⊆ G.
4 5

© Alberto Montresor 75
Soluzione errata

✦ Proviamo ad utilizzare l’algoritmo per le componenti connesse?


✦ Risultati variano a seconda del nodo da cui si parte

2
3
1
6

4 5

© Alberto Montresor 76
Componenti fortemente connesse

✦ Algoritmo (Kosaraju, 1978)


1. Effettua una DFS di G
2. Calcola il grafo trasposto GT
3. Effettua una DFS di GT esaminando i vertici in ordine inverso di tempo di fine
4. Fornisci i vertici di ogni albero della foresta depth-first prodotta al passo 3.
come una diversa SCC

✦ Grafo trasposto
✦ Dato un grafo G = (V, E), il grafo trasposto GT = (V, ET ) è formato dagli stessi
nodi, mentre gli archi hanno direzioni invertite: i.e, ET ={(u,v) | (v,u) ∈ E}

© Alberto Montresor 77
al termine di ogni visita. La pila verrà svuotata in ordine LIFO in un vettore, che verrà passato
Componenti
alla fortemente
procedura cc connesse
() come ordine - algoritmo
di visita.

integer[ ] scc(G RAPH G)


S TACK S Stack() % Prima visita
boolean[ ] visitato new boolean[1 . . . G.n]
foreach u ⇥ G.V() do visitato[u] false
foreach u ⇥ G.V() do dfsStack(G, visitato, S, u)
G RAPH GT Graph() % Calcolo grafo trasposto
foreach u ⇥ G.V() do GT .insertNode(u)
foreach u ⇥ G.V() do
foreach v ⇥ G.adj(u) do
GT .insertEdge(v, u)

integer[ ] ordine new integer[1 . . . G.n] % Seconda visita


for integer i 1 to n do ordine[i] S.pop()
return cc(GT , ordine)

dfsStack(G RAPH G, boolean [ ] visitato, S TACK S, N ODE u)


78
visitato[u]
© Alberto Montresor
true
return cc(GT , ordine)
Componenti fortemente connesse - algoritmo

dfsStack(G RAPH G, boolean [ ] visitato, S TACK S, N ODE u)


visitato[u] true
foreach v ⇥ G.adj(u) do
if not visitato[v] then
dfsStack(G, visitato, S, v)
S.push(u)

© Alberto Montresor 79
Componenti fortemente connesse - dimostrazione correttezza

✦ Domanda
✦ Che rapporto c’è fra le SCC del grafo G e del suo trasposto GT

✦ Grafo delle componenti Gc = (Vc, Ec)


✦ Vc = {C1, C2, . . . , Ck}, dove Ci è l’i-esima componente connessa di G;
✦ Ec ={(Ci,Cj): ∃(ui,uj) ∈ E :ui ∈Ci,uj ∈Cj}

Grafo delle
componenti 2
è aciclico? 3
1
6

4 5

© Alberto Montresor 80
to di tempo di inizio e di fine per le componenti. Sia C un sottoin-
Componenti
(C) come il primo fortemente connesse
tempo di scoperta - dimostrazione
e ft(C) come l’ultimocorrettezza
tempo di
✦ Si può estendere il concetto di dt e ft al grafo delle componenti

dt(C) = min{dt[u] : u ⇥ C}
ft(C) = max{ft[u] : u ⇥ C}
✦ Teorema
ni, possiamo enunciare il seguente teorema.
✦ Siano C e C′ due componenti distinte nel grafo orientato G = (V, E). Suppo-
C due componenti distinte nel grafo orientato cG = (V, E). Suppo-
niamo che esista un arco (C, C′) ∈ E . Allora ft(C) > ft(C′)
(C, C ) ⇥ E . Allora ft(C) > ft(C ).
c
[9,10]
[2,11] [1, 8]
ati due casi: viene scoperto prima un nodo di C
[3,10] oppure di C . 2
[11,12]
[1,12] 2 3
x il primo nodo scoperto in C, all’istante 3 dt(C). Poiché tutti i nodi
1
ungibili1 da x e non ancora scoperti, la loro visita inizierà e terminerà
ione della visita di x. Quindi ft(C) = ft(x) > ft(y), per ogni y ⇥ [5,7] 6
gue che ft(C) > ft(C ). [7,8] 6 [2, 7]
[4,9] 4 5
y il primo nodo scoperto 4 in C ; tutti i nodi in C sono raggiungibili
5
operti. Al termine della visita di tutti i nodi di C , tutti i nodi[3,4] di C
scoperti. La loro [5,6]
visita inizierà e terminerà dopo ft(C ); ne segue che 81
© Alberto Montresor
ro quindi parte di un’unica componente, una contraddizione.
to diComponenti
tempo di inizio e di fine per
fortemente le componenti.
connesse Sia C un
- dimostrazione sottoin-
correttezza
(C) come il primo tempo di scoperta e ft(C) come l’ultimo tempo di
✦ Si può estendere il concetto di dt e ft al grafo delle componenti

dt(C) = min{dt[u] : u ⇥ C}
ft(C) = max{ft[u] : u ⇥ C}
✦ Teorema
ni, possiamo enunciare il seguente teorema.
✦ Siano C e C′ due componenti distinte nel grafo orientato G = (V, E). Suppo-
C due componenti distinte nel grafo orientato cG = (V, E). Suppo-
niamo che esista un arco (C, C′) ∈ E . Allora ft(C) > ft(C′)
(C, C ) ⇥ E . Allora ft(C) > ft(C ).
c
[9,10]
[1, 8]
ati due casi: viene scoperto
[11,12] prima un nodo 2di C oppure di C .
3
x il primo nodo scoperto in C, all’istante dt(C). Poiché tutti i nodi
1
ungibili da x e non ancora scoperti, la loro visita inizierà e terminerà
ione della visita di x. Quindi ft(C) = ft(x) > ft(y), per ogni y ⇥ 6 [5,7]
gue che ft(C) > ft(C ). [2, 7]
4 5
y il primo nodo scoperto in C ; tutti i nodi in C sono raggiungibili
operti. Al termine della visita di tutti i[3,4]
nodi di C , tutti i nodi di C 82
© Alberto Montresor