Nei grafi pesati ad ogni arco è associato un
peso (o costo).
Il costo può essere rappresentato da una funzione di costo
w: E --> R
Quando tra due vertici non esiste un arco, si dice che il costo è infinito.
Tipi di rappresentazione standard per grafi
a. rappresentazione a matrice di adiacenza.
| 1 se (u,v) appartiene E M(u,v) = < | 0 altrimenti
|
Nel caso di grafi non orientati la matrice è simmetrica.
Nel caso di grafi pesati l'elemento
mv,u qualora non sia nullo assumo il valore del peso dell'arco.
Spazio richiesto:
O( |V|2 )
Verificare se i vertici
u, v sono adiacenti richiede tempo
O( 1 )
Molti zero nel caso di grafi sparsi (poso densi)
b. rappresentazione a lista di adiacenzaL(v) = lista di u, tale che (u,v) appartiene E per v appartente V
|
Algoritmi di visita
L'operazione corrispondente ad esplorare un grafo a partire da un vertice dato (sorgente) toccando tutti i vertici da questo raggiungibili si definisce
visita.
Esistono due tipo di visita:
- Visita in ampiezza: Breadth-First-Search
Consiste nel visitare i vertici del grafo per livelli. - Visita in profondità: Depth-First-Search
Ad ogni passo i si visita un vertice adiacente all'ultimo vertici visitato esplorando il grafo in profondità. Quando non ne esistono, si torna indietro all'ultimo vertice visitato che abbia vertici adiacenti non visitati, e li si visita.
BFS: Visita in ampiezza
Consiste nel visitare i vertici del grafo per livelli.
Viene normalmente implementata utilizzando una coda FIFO.
- un vertice è stato visitato se è apparso nella coda: gray
- un vertice non è stato visitato se non è ancora stato visitato: white
- un vertice è stato processato se tutti i vertici ad esso adiacenti sono stati visitati: black
L'algoritmo BFS eseguito su un grafo
G = (V, E) costruisce nell'array
pred[] il sottografo dei predecessori.
Cammini minimi: dati due vertici
s e
v in un grafo non pesato, il numero minimo di archi su un cammino da
s a
v si definisce distanza sul cammini minimo. L'algoritmo BFS calcola le distanze sul cammini minimo per ogni vertice
v di
V (rispetto ad un vertice sorgente).
BFS(G, s)
for each vertice u appartente V[G] -{s} color[u] <-- White path[u] <-- Inf pred[u] <-- NULL color[s] <-- Gray path[s] <-- 0 pred[s] <-- NULL ENQUEUE(Q, s)
while ( Q != vuota ) u <-- DESQUEUE(Q)
for each vertice v adiacente a u if ( color[v] == White ) color[v] <-- Gray path[v] <-- path[u] +1 pred[v] <-- u ENQUEUE(Q, v)
color[u] <-- Black
|
Inizializzazione O( |V| )
Per ogni vertice, si tenta di passare sui suoi archi, complessivamente quindi O( |E| )
|
Complessità totale:
O( |V| + |E| )
DFS: Visita in profondità
Ad ogni passo
i si visita un vertice adiacente all'ultimo
vertici visitato esplorando il grafo in profondità. Quando non ne
esistono, si torna indietro all'ultimo vertice visitato che abbia
vertici adiacenti non visitati, e li si visita.
Viene normalmente implementata tramite una procedura ricorsiva.
Il grafo è connesso <==> una chiamata di DFS raggiunge tutti i vertici (se al termine della DFS c'è più di un vertice
u con
pred[u] = NULL il grafo non può essere connesso, equivalentemente si guarda lo stato di visitato)
DFS(G)
for each vertice u appartente V[G]
color[u] <-- White
pred[u] <-- NULL time <-- 0
for each vertice u appartenente V[G] if ( color[u] = White ) DFS-Visit(u)
DFS-Visit(u)
color[u] <-- Gray path[u] <--time <-- time + 1
for each vertice v adiacente a u if ( colour[v] == White ) pred[v] <-- u DFS-Visit(v)
color[u] <-- Black f[u] <-- time <-- time + 1
|
Inizializzazione Theta( |V| )
Theta( |V| )
DFB chiamata V volte, check su ogni vertice adiacente --> theta ( |E| )
|
Complessità totale:
Theta( |V| + |E| )Alberi di copertura minimi Minimum Spanning Tree
Sia
G = (V, E) un grafo non orientato, connesso e pesato con una funzione peso
w: E --> R.
L'albero di copertura minimo (MST) è il grafo
G' avente le seguenti proprietà:
- è composto dagli stessi vertici di G e da un sottoinsieme T degli archi E (T contenuto E)
- è connesso e aciclico
- minimizza la quantità w(T) = sum( w(u,v) )
(Minimizzare la lunghezza dei collegamenti da realizzare)
L'albero di copertura minimo per un grafo in generale non è unico.
Gli algoritmi
greedy (ingordo) sono algoritmi basati sull'idea di fare sempre scelte che sembrano ottime al momento della scelta.
Non è sempre garantita la correttezza, ma, se lo è, sono in genere molto semplici ed efficienti.
Geniric-MST(G, w)
A <-- Ø while ( A non forma un albero di copertura ) trova un arco (u,v) che sia sicuro per A A <-- Union( A, {(u, v)} )
return A
|
Si definisce
sicuro un arco che aggiunto ad un sottoinsieme di arco di copertura minimo produce ancora un sottoinsieme di un albero di copertura minimo.
Dato un grafo non orientato
G = (V, E), un
taglio è una partizione di
V in due sottoinsiemi.
Un
arco attraversa il taglio se uno dei suoi estremi è in una partizione e l'altro nell'altra.
Un
taglio rispetta un insieme di archi A se nessun arco di
A attraversa il taglio.
Un arco si dice
leggero se ha preso minimo tra gli archi che attraversano l'arco.
Teorema
Sia
G = (V, E) un grafo non orientato, connesso e pesato da una funzione peso
w: E --> R.
Sia
A un sottoinsieme di archi
E contenuto in uno degli alberi di copertura minimi per
G.
Sia
(S, V-S) un qualunque taglio che rispetta A.
Sia
(u,v) un arco leggero che attraversa
(S, V-S)
Allora l'arco
(u, v) è
sicuro per
A.
CorollarioSia
G = (V, E) un grafo non orientato, connesso e pesato da una funzione peso
w: E --> R.
Sia
A un sottoinsieme di archi
E contenuto in uno degli alberi di copertura minimi per
G.
Sia
C una componente connessa (ossia un albero) della foresta
GA = (V, A)Sia
(u,v) un arco leggero che connette
C ad un'altra componente in
GAAllora l'arco
(u, v) è
sicuro per
A.
Se abbiamo un grafo G e abbiamo un parziale albero di copertura, se facciamo un taglio che rispetta questo, allora l'arco leggero che attraversa il taglio è un arco sicuro.