Cammini minimi con una
sorgente
Problema dei cammini minimi
Varianti e archi negativi
Sottostruttura ottima di un cammino minimo
Algoritmo di Dijkstra
Complessità dell’algoritmo
Rappresentazione dei cammini minimi
Problema dei cammini minimi
Input: un grafo G=(V,E) orientato e pesato, con una
funzione peso w: E → R, che associa ad ogni arco in E
un peso a valore reale.
1
9
8
1
2
1
3
1
1
3
1
1
6
6
4
-3
5
-1
1
3
3
4
2
6
1
1
4
1
5
1
Problema dei cammini minimi
Il costo di un cammino p= <v1, v2, …,vk> è la somma
dei pesi degli archi che lo costituiscono.
k
w ( p )=∑ w( v i−1 ,v i )
i= 2
15 min + 50 min = 65 min
50
MC
20
p1= <PR, FI, SI> =
PT
LU
20
35
25
20
LI
PR
60
PI
FI
35
25
50
65
SI
20
GR
p2 = <PR, FI, AR, SI> =
15
40
AR
15 min + 35 min + 40 min =
= 90 min
p3 = <PR, FI, PI, LI, GR,
SI> = 160 min
Problema dei cammini minimi
Il costo di un cammino minimo dal vertice u al vertice
v è definito da:
{
min {w( p) :u ⃗
p v } se esiste un cammino da u a v
∞
altrimenti
Un cammino minimo dal vertice u al vertice v è
definito come un qualunque cammino p con peso
w(p) = δ(u,v). Può non essere unico!
δ ( u,v )=
δ(6,1) = 7
9
8
1
2
1
3
p1 = <6, 2, 3, 1 >
3
3
3
3
4
-2
5
6
p2 = <6, 1>
w(p1) = 7
w(p2) = 9
p3 = <6, 5, 6, 2, 3, 1 >
-1
w(p3) = 9
p4 = <6, 5, 4, 3, 1 > w(p4) = 7
}
Vari problemi
• Problema di cammini minimi con sorgente singola: si
vuole trovare un cammino minimo da un dato vertice
sorgente s ad ogni vertice v in V.
• Problema di cammini minimi con destinazione
singola: si vuole trovare da ogni vertice v in V un
cammino minimo ad un dato vertice destinazione t.
• Problema di cammini minimi tra una coppia: si vuole
trovare un cammino minimo da u a v.
• Problema di cammini minimi tra tutte le coppie:
determinazione di un cammino minimo per ogni coppia di
vertici u e v.
Archi con pesi negativi
NOTA: sono ammessi archi di peso negativo, ma non
devono esistere nel grafo cicli di costo negativo.
Se il costo di un ciclo è negativo, allora tutti i nodi
raggiungibile dal ciclo hanno un cammino minimo di
peso infinitamente negativo (-∞).
Ciclo <6,5> negativo.
9
8
1
1
3
Ogni volta che compio un giro
diminuisco il peso del cammino
che passa per il ciclo.
2
3
3
3
3
4
-2
5
6
-7
Esempio: δ(6,1) = -∞
Sottostruttura ottima di un cammino minimo
Sottocammini di cammini minimi sono cammini minimi
Dato un grafo G=(V,E) con funzione peso w:E→ R, sia
p= <v1, v2, …,vk> un cammino minimo da v1 a vk. Per ogni i
e j tali che 1 ≤ i ≤ j ≤ k, si ha che il sottocammino
pij= <vi, vi+1, …,vj> è un cammino minimo da i a j.
w(pij)=δ(i,j)
1
i
j
k
p’ij
Dato un altro sottocammino p’ da i a j, necessariamente
ij
w(p ) ≤ w(p’ ), altrimenti il cammino minimo passerebbe per
ij
ij
p’ij
Sottostruttura ottima di un cammino minimo
Di conseguenza:
Si supponga che un cammino minimo p da una sorgente s
ad un vertice v passi per l’arco (u,v) con peso w(u,v). Il peso
del cammino minino da s a v è δ(s,v) = δ(s,u) + w(u,v).
δ(s,u)
i
cammino minimo tra u e v
w(u,v)
u
v
δ(s,v) = δ(s,u) + w(u,v).
Più in generale, se esiste un arco (x,v), allora si ha:
δ(s,v) ≤ δ(s,x) + w(x,v), vale l'uguaglianza se x = u.
Algoritmo di Dijkstra
L’algoritmo di Dijkstra risolve il problema dei cammini
minimi con sorgente singola S su un grafo orientato (o
non orientato) e pesato G=(V,E) nel caso in cui tutti i pesi
degli archi siano non negativi.
Ci sono due insiemi:
• S: dove d[v] = δ(s,v), quindi un cammino minimo tra s e v
è stato determinato.
• Q = V/S: una coda a priorità dove d[v] ha il valore del
cammino con peso minore finora scoperto.
All’inizio, S contiene solo s, d[s]=0, mentre Q=V / {s} con
d[v]=∞.
Algoritmo di Dijkstra
DIJKSTRA(G,w,s)
1.
for ogni vertice u in V[G]
// inizializzazione di ogni vertice
2.
do d[u] ← ∞
3.
p[u] ← NIL
4.
d[s] ← 0
// si comincia dal vertice s
5. Q ← V[G]
// coda a priorità
6. S ← Ø
// insiemi dei nodi con cammino minimo trovato
7.
while Q≠Ø
// termina quando la coda Q è vuota
8.
do u ← EXTRACT-MIN(Q) // prendi il nodo u con d[u] minore
9.
S ← S U {u}
// inserisci u in S
for ogni vertice v in Adj[u] / S
10.
do if d[v] > d[u] + w(u,v)
// Procedura relax(u,v,w)
11.
then d[v] ← d[u] + w[u,w] // aggiorna cammini minimi
per v adiacente u
12.
p[v] ← u
13.
Q.decrease.key(v, d[v]) // Aggiorna le distanze in Q
Esempio
u
10
3
s
0
∞
2
1
∞
9
u
10
v
3
s
4 6
0
10
2
1
9
∞
x
5
5
y
x
(a)
3
s
0
2
5
13
9
4 6
x
2
(d)
5
5
y
9
4 6
x
3
s
0
1
8
2
y
5
5
9
9
4 6
x
2
(e)
y
u
10
3
s
0
v
1
8
2
9
9
4 6
7
7
7
7
2
(c)
v
7
5
2
14
7
∞
2
u
10
v
1
8
1
8
(b)
u
10
0
v
7
∞
2
3
s
4 6
7
5
∞
u
10
v
7
y
5
5
x
2
(f)
7
y
Esempio
4
3
1,3
2,3
6
0,9
1
2
0,5
7
5
2
0,4
Complessità
Si consideri che la coda con priorità Q come un array
lineare:
• Inizzializzazione tempo O(|V|)
• EXTRACT-MIN(Q): ricerca del minimo in Q. Bisogna
vedere tutti i valori in Q, richiede tempo O(|V|)
– EXTRACT-MIN(Q) viene eseguito per ogni vertice quindi il tempo
totale è O(|V|x|V|) = O(|V|2)
• Come in BFS vengono, si esamina la lista di adiacenza
di ogni vertice v, che entra solo una volta in S. La somma
di tutte liste di adiacenze è |E|, più il costo delle descrease
key totale O(|V||E|)
• Il tempo totale dell’algoritmo di Dijkstra è O(|E|+|V|2).
Complessità
L’algoritmo di Dijkstra con binary heap: l’analisi
1. Inizializzazione di Q

Build-Heap che costruisce un heap con |V| elementi in O(V)
2. Analisi del ciclo di while

V operazioni Extract-Min


(totale O(VlogV))
V aggiornamenti di S


Ciascuna richiede tempo O(log V)
Ciascuna richiede tempo costante
Un totale di O(E) iterazioni del ciclo di for (linee 10-13) in quanto la
lista di adiacenza di ciascun vertice u viene scandita esattamente una
volta (quando u è inserito in S)

RELAX(u, v, w) sulla linea 8 :

L’aggiornamento di d[v] è effettuato eseguendo DECREASEKEY( Q,
v , d[u] + w(u, v)) che richiede tempo O(log V) [ma nel codice matlab
O(n) per la ricerca della posizione di v nello heap !]. Totale O(ElogV)
L’aggiornamento di π[v] richiede tempo costante
Tempo totale: O(Vlog V + E log V)

Rappresentazione dei cammini
minimi
Come nella visita in ampiezza (BFS), l’algoritmo Dijkstra
definisce un sottografo dei predecessori Tp=(Vp,Ep).
L’albero che ne segue contiene i cammini minimi individuati
da s ad ogni vertice raggiungibile v.
Nota: questo vale solo al termine dell’algoritmo.
u
10
3
s
0
1
8
2
u
v
9
9
4 6
3
s
v
1
8
9
Tp=(Vp,Ep)
0
7
5
5
x
2
7
y
5
5
x
2
7
y
Problemi Algoritmo Dijkstra
v

1
0
0
s
v
1
1
0
s
1
1
1
1
1
1
w

3

t
w

3
1
1
t
1
x

1
-5
2

u
1
x

1
-5
2

u
1
v
1
1
0
s
v
1
1
0
s
1
1
1
1
1
1
w
4
3
1
t
2
w
2
3
1
t
1
x

1
-5
2
3
u
1
x

1
-5
2
3
u
Problemi Algoritmo Dijkstra
v
1
1
0
s
v
1
1
0
s
1
1
1
1
1
1
2
w
2
3
1
t
w
2
3
1
t
1
x

v
1
1
-5
2
3
u
0
s
3
x
3
v
1
1
1
-5
2
3
u
1
1
0
s
1
1
1
1
1
1
w
2
1
x
3
1
-5
1
t
2
3
4
u
w
2
1
x
3
3
3
1
t
1
-5
2
3
u
Problemi Algoritmo Dijkstra
v
1
1
0
s
1
1
1
w
2
3
1
t
1
x
3
1
-5
2
3
u
Soluzione di Dijkstra
v
0
1
1
1
0
s
1
w
-1
3
1
t
1
x
-2
1
-5
2
3
u
Soluzione Ottimale
Algoritmo di Bellman-Ford
Ricordiamo che per ogni arco (u,v) in E e per ogni
vertice s in V, vale:

Tecnica del Rilassamento:


Si parte da stime per eccesso delle distanze Dxy ≥ dxy
Si aggiornano le stime, decrementandole
progressivamente fino a renderle esatte.
Algoritmo di Bellman-Ford
Esegue n iterazioni

In ciascuna iterazione rilassa tutti gli archi

Dopo la j-esima passata, i primi j rilassamenti
corretti sono stati certamente eseguiti


Esegue però molti rilassamenti inutili!
Algoritmo di Bellman-Ford
• Inizalmente, d[s] = 0 e d[u] =  per u  s
• Vengono fatte |V| - 1 passate
• Ad ogni passata, applica il rilassamento ad ogni arco
Tempo = O(|V||E|)
Bellman_Ford(G,s)
Inizializza(G,s,d)
for i = 1 to |V|-1
do for each arco (u,v) in E
do relax(u,v,w)
for each arco (u,v) in E //controllo
presenza cicli nagativi
do if d[v] > d[u] + w(u,v)
then return FALSE
return TRUE
Esempio Algoritmo Bellman-Ford
v

1
0
s
v
1
1
0
s
1
1
1
1
1
1
w

1
x

v
1
1
-5

t
2

u
0
s
w
4
1
x

v
1
3
3
1
t
1
-5
2
3
u
1
1
0
s
1
1
1
1
1
1
w

1
x

1
-5
1
t
2

u
w
2
1
x
-2
3
3
1
t
1
-5
2
3
u
Esempio Algoritmo Bellman-Ford
v
1
1
0
s
1
1
1
w
2
x
-2
1
1
3
1
t
1
0
s
1
1
1
0
s
3
u
1
1
1
-5
2
v
0
v
1
w
-1
3
1
t
1
1
x
-2
1
-5
2
3
u
w
-1
3
1
t
1
x
-2
1
-5
2
3
u
Esercizi
1. Implementare l'oggetto MATLAB grafo non diretto
pesato, in cui gli archi hanno un peso. Implementare la
lista di adiacenza come lista concatenata in cui gli
elementi sono strutture con campi nodo e peso
2. Scrivere un programma MATLAB che implementi
l'algoritmo di Bellman-Ford su grafo (diretto o non
diretto) rappresentato mediante lista di adiacenza
3. Scrivere un programma MATLAB che permette
all'utente di gestire un grafo diretto pesato (inserire
vertici ed archi,cancellare archi) e calcolare i cammini
minimi tra due vertici specificati mediante nome