Capitolo 6
Problemi di Cammino Minimo
Dato un grafo orientato D = (V, A), diciamo che sono specificati costi sugli archi di D se è
assegnato un valore ce per ogni e ∈ A. Dato un sottoinsieme di archi S ⊆ A, chiamiamo costo
di S il numero c(S) = ∑e∈S ce .
Il problema del cammino minimo è il seguente: dati un grafo orientato D = (V, A) con costi
(ce )e∈A sugli archi ed un nodo s ∈ V (detto sorgente), trovare, per ogni v ∈ V , un cammino
orientato di costo minimo da s a v (o stabilire che non c’è alcun cammino orientato da s a
v).1
Nel resto del capitolo lavoreremo sempre con cammini e cicli orientati. Pertanto, spesso
diremo semplicemente “cammino” o “ciclo” invece di “cammino orientato” e “ciclo orientato”.
Un cammino minimo da s a v è un cammino di costo minimo da s a v.
Se non si impone alcuna condizione sui costi, non è noto alcun algoritmo polinomiale per
risolvere il problema del cammino minimo. Si può dimostrare che se esistesse un tale algoritmo, allora potremmo risolvere in tempo polinomiale anche il problema del ciclo hamiltoniano
su un qualunque grafo (Esempio 5.8), cosa che, come detto nel capitolo precedente, è ritenuta
molto improbabile a causa di profonde motivazioni teoriche.
Per vedere come un eventuale algoritmo polinomiale per il problema del cammino minimo
potrebbe essere usato per risolvere il problema del ciclo hamiltoniano, procediamo come segue.
Supponiamo che esista un tale algoritmo A per il problema del cammino minimo e sia G =
(V, E) una qualunque istanza del problema del ciclo hamiltoniano, che vogliamo risolvere.
Scegliamo arbitrariamente un nodo s ∈ V . Si noti che se esiste un ciclo hamiltoniano in G,
allora tale ciclo contiene almeno uno spigolo e ∈ δ(s) (in realtà ne contiene esattamente due).
Allora, per decidere se G ha un ciclo hamiltoniano, è sufficiente verificare, per ogni e ∈ δ(s),
se G ha un ciclo hamiltoniano contenente lo spigolo e. Fissato uno spigolo e ∈ δ(s), diciamo
e = st, facciamo quanto segue: rimuoviamo e da G, ottenendo un nuovo grafo G′ ; costruiamo
un grafo orientato D a partire da G′ , sostituendo ogni spigolo uv ∈ E con una coppia di archi
opposti (u, v), (v, u); diamo costo −1 a tutti gli archi di D. Poiché un cammino non può
visitare due volte lo stesso nodo, ogni cammino in D ha al massimo n − 1 archi (dove n è il
numero di nodi del grafo), dunque il costo di un cammino minimo da s a t è sempre almeno
−(n − 1); inoltre, tale costo è esattamente −(n − 1) se e solo se esiste un cammino da s a t che
1
In alcune formulazioni, nel problema del cammino minimo sono fissati due nodi s e t (dove t è detto
terminazione o pozzo) e si richiede di trovare il cammino orientato di costo minimo da s a t. Tuttavia, quasi
tutti gli algoritmi per questo problema calcolano contemporaneamente un cammino di costo minimo da s a
qualunque terminazione v ∈ V , senza che questo richieda alcuno sforzo aggiuntivo. Per questo motivo non
fissiamo la terminazione.
63
64
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
passi per tutti i nodi di D. In tal caso, ignorando l’orientamento degli archi e ripristinando
lo spigolo e otteniamo un ciclo hamiltoniano in G; in caso contrario, invece, G non ha alcun
ciclo hamiltoniano contenente lo spigolo e. Eseguendo dunque queste operazioni per ogni
e ∈ δ(s), possiamo scoprire se G contiene un ciclo hamiltoniano. Questa procedura richiede
l’applicazione dell’algoritmo A esattamente ∣δ(s)∣ volte (e quindi meno di n volte); dunque,
se A è polinomiale, ne risulta una procedura polinomiale.
Il problema del cammino minimo sembra dunque di difficile soluzione quando i costi sono
arbitrari. Questo problema può tuttavia essere risolto in tempo polinomiale se il grafo non
contiene alcun ciclo orientato di costo negativo, cioè se G non ha alcun ciclo orientato C tale
che c(C) = ∑e∈A(C) < 0. Si noti che questa condizione è sempre soddisfatta, ad esempio, se i
costi sono tutti non-negativi (caso di evidente interesse pratico).
6.1
Cammini di lunghezza minima
Ci concentriamo prima sul caso particolare in cui ce = 1 per ogni e ∈ A; in questa situazione, il
costo di un cammino è pari al numero di archi che lo compongono, cioè alla sua lunghezza. Il
problema è dunque il seguente: dati un grafo orientato D = (V, A) ed un nodo s ∈ V , trovare,
per ogni v ∈ V , un cammino di lunghezza minima da s a v (o stabilire che non c’è alcun
cammino da s a v).
Sia n = ∣V ∣. Si noti che qualunque cammino in D ha lunghezza inferiore o uguale a n − 1.
Per ogni nodo v ∈ V , definiamo distanza di v da s la lunghezza di un cammino da s a v che
ha il numero minimo di archi. Per k = 0, . . . , n − 1, indichiamo con Vk l’insieme dei nodi a
distanza k da s. In altre parole un nodo v appartiene a Vk se e solo se il cammino minimo da
s a v ha lunghezza k.2 L’algoritmo proposto per la risoluzione del problema in esame si basa
sulle seguenti osservazioni.
Lemma 6.1 V0 = {s}, mentre, per k = 1, . . . , n − 1,
Vk = {v ∈ V ∖ (V0 ∪ V1 ∪ . . . ∪ Vk−1 ) ∶ (u, v) ∈ A per qualche u ∈ Vk−1 }.
(6.1)
Dimostrazione. È chiaro che V0 = {s}. Fissiamo ora k ∈ {1, . . . , n − 1} e definiamo Sk come
l’insieme a secondo membro dell’uguaglianza (6.1). Vogliamo mostrare che Vk = Sk .
Se v ∈ Vk allora necessariamente v ∉ V0 ∪ ⋅ ⋅ ⋅ ∪ Vk−1 . Inoltre, esiste un cammino minimo
P da s a v la cui lunghezza è k. Sia u il nodo che precede v in P . Dimostriamo che il
sottocammino Psu è un cammino di lunghezza minima da s a u. Supponiamo per assurdo che
esista un cammino Q da s a u di lunghezza minore di quella di Psu . Se Q non contenesse v,
allora la sequenza Q, (u, v), v formerebbe un cammino da s a v di lunghezza minore di quella
di P , una contraddizione. Se, invece, Q contenesse v, allora Qsv sarebbe un cammino da s a v
di lunghezza minore di quella di P , di nuovo una contraddizione. Dunque Psu è un cammino
di lunghezza minima da s a u, il che implica che u ∈ Vk−1 . Questo mostra che Vk ⊆ Sk .
Per verificare l’inclusione inversa, osserviamo che se v ∈ Sk allora la distanza di v da s è
almeno k. D’altro canto, se v ∈ Sk allora esiste u ∈ Vk−1 tale che (u, v) ∈ A. Poiché u ∈ Vk−1 ,
esiste un cammino P da s a u di lunghezza k − 1. Si noti che v non può essere un nodo di
P , altrimenti la sua distanza da s sarebbe inferiore a k. Allora la sequenza P, (u, v), v è un
2
Sarebbe più corretto dire “i cammini minimi da s a v hanno lunghezza k”, visto che il cammino minimo
non è necessariamente unico (ma tutti i cammini minimi da s a v hanno ovviamente la stessa lunghezza).
6.1. CAMMINI DI LUNGHEZZA MINIMA
65
cammino da s a v di lunghezza k, quindi v ha distanza al massimo k da s. Questo implica
che la distanza di v da s è esattamente k, cioè v ∈ Vk . Dunque Sk ⊆ Vk .
◻
Lemma 6.2 Sia (u, v) ∈ A, dove u ∈ Vk−1 e v ∈ Vk . Se Q è un cammino di lunghezza minima
da s a u, allora la sequenza Q, (u, v), v costituisce un cammino di lunghezza minima da s a
v.
Dimostrazione. La dimostrazione è immediata, una volta osservato che v ∉ V (Q) (altrimenti
la sua distanza da s sarebbe inferiore a k, come mostrato dal cammino Qsv ).
◻
L’algoritmo che proponiamo per determinare i cammini minimi dal nodo sorgente s ad
ogni nodo v ∈ V costruirà iterativamente l’insieme Vk a partire dagli insiemi V0 , . . . , Vk−1 :
questo può essere fatto grazie al Lemma 6.1. Allo stesso tempo, registreremo i predecessori
di ogni nodo, cioè per ogni nodo v ∈ V memorizzeremo un nodo u tale che u, v soddisfino le
ipotesi del Lemma 6.2 (in molti casi la scelta di u non è univoca, ma questo non crea alcuna
difficoltà). Con queste informazioni avremo la soluzione del problema: per ogni nodo v, detto
k l’indice tale che v ∈ Vk , sapremo che la lunghezza di un cammino minimo da s a v è k;
inoltre, detto u il predecessore di v memorizzato dall’algoritmo, sapremo che un cammino
minimo da s a v può essere costruito aggiungendo l’arco (u, v) ad un qualunque cammino
minimo da s a u (Lemma 6.2); un tale cammino minimo può essere determinato a sua volta
verificando qual è il predecessore del nodo u registrato dall’algoritmo e procedendo a ritroso,
fino a risalire alla sorgente s.
Formalmente, l’algoritmo manterrà, per ogni nodo v ∈ V , la distanza d(v) di v da s (dove
d(v) = +∞ se tale distanza non è ancora stata determinata) ed un puntatore p(v) ad un
nodo u tale che (u, v) ∈ A e d(u) = d(v) − 1. Manterremo anche aggiornato l’insieme R dei
nodi già raggiunti (cioè quelli per cui è stata determinata la distanza da s). Diamo qui sotto
la descrizione formale dell’algoritmo; si veda la Figura 6.1 per un esempio di applicazione
dell’algoritmo.
Algoritmo per cammini di lunghezza minima
Input: Un grafo orientato D = (V, A) ed un nodo s ∈ V .
Output: Per ogni v ∈ V , la lunghezza d(v) di un cammino di lunghezza
minima da s a v ed un nodo p(v) che precede v in un tale cammino (se
d(v) < +∞; altrimenti p(v) non è definito).
1. Si ponga d(s) ∶= 0, d(v) ∶= +∞ ∀v ∈ V ∖ {s}, V0 ∶= {s} e R ∶= {s};
2. Per ogni k = 1, . . . , n − 1:
2.1. Si ponga Vk ∶= ∅;
2.2. Per ogni arco (u, v) tale che u ∈ Vk−1 e v ∉ R:
si ponga d(v) ∶= k, p(v) ∶= u, Vk ∶= Vk ∪ {v} e R ∶= R ∪ {v}.
Teorema 6.3 L’algoritmo per cammini di lunghezza minima funziona correttamente ed esegue O(∣V ∣ + ∣A∣) operazioni.
66
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
Dimostrazione. Sia k ∈ {1, . . . , n − 1}. Alla fine della k-esima iterazione dell’istruzione 2, gli
insiemi V0 , . . . , Vk sono stati costruiti correttamente ed inoltre R = V0 ∪ ⋅ ⋅ ⋅ ∪ Vk : questo segue
dalle istruzioni dell’algoritmo e dal Lemma 6.1. Si noti inoltre che d(v) < +∞ se e solo se
v ∈ R (e in tal caso p(v) è definito e non muterà più). Pertanto, dopo l’ultima iterazione, a
ciascun nodo v ∈ R è stato assegnato il corretto valore d(v) ed il corretto predecessore p(v). I
nodi v ∉ R, invece, hanno d(v) = +∞: questi sono i nodi per i quali non esiste alcun cammino
da s a v (se ci fosse un tale cammino, l’avremmo trovato prima della fine dell’algoritmo, dato
che nessun cammino in D ha lunghezza maggiore di n − 1).
Per quanto riguarda la complessità dell’algoritmo, il passo 1 e tutte le iterazioni del passo 2.1 richiedono complessivamente tempo O(∣V ∣). Inoltre, ogni arco (u, v) viene analizzato
al massimo una volta durante le varie iterazioni dell’istruzione 2.2 (con un numero costante
di operazioni per ogni arco). Ne segue che l’algoritmo esegue O(∣V ∣ + ∣A∣) operazioni.
◻
Osserviamo che può accadere di trovare R = V ben prima di raggiungere l’ultima iterazione
dell’algoritmo; in tal caso si può interrompere l’esecuzione, in quanto tutti i cammini minimi
sono stati determinati. In modo analogo, se dopo la k-esima iterazione si dovesse avere Vk = ∅,
allora potremmo interrompere l’esecuzione, in quanto proseguendo troveremmo Vk+1 = ⋅ ⋅ ⋅ =
Vn−1 = ∅.
Come visto, al termine dell’algoritmo l’insieme R conterrà esattamente i nodi raggiungibili
da s, cioè i nodi v ∈ V per i quali esiste un cammino da s a v. Per tali nodi, inoltre, p(v)
è definito. Sia T = (R, A′ ) il sottografo di D con insieme dei nodi R ed insieme degli archi
A′ = {(p(v), v) ∶ v ∈ R ∖ {s}}. È semplice dimostrare quanto segue.
Osservazione 6.4 T è un’arborescenza con radice s. Per ogni v ∈ R, l’unico cammino da s
a v in T è un cammino di lunghezza minima d(v).
Dimostrazione. Per ogni v ∈ R, il grafo T contiene un cammino orientato da s a v (questo segue
dalla correttezza dell’algoritmo). Per costruzione, ∣A′ ∣ = ∣R∣ − 1. Dunque T è un’arborescenza
con radice s. La seconda affermazione segue ancora dalla correttezza dell’algoritmo.
◻
Si dice che T è un albero di cammini di lunghezza minima di D (si veda di nuovo la
Figura 6.1, da cui si evince anche che tale albero non è unico).
6.2
L’algoritmo di Bellman–Ford
L’algoritmo di Bellman–Ford risolve il seguente problema: dato un grafo orientato D = (V, A)
con costi (ce )e∈A sugli archi, dove D non contiene alcun ciclo di costo negativo, e dato un
nodo sorgente s ∈ V , trovare, per ogni v ∈ V , un cammino di costo minimo da s a v (o stabilire
che non c’è alcun cammino da s a v).
Da ora in avanti, dato un arco (u, v) ∈ A, indicheremo il suo costo con cuv invece di usare
la notazione più pesante c(u,v) .
Sia n = ∣V ∣. Dato un cammino orientato P da s ad un nodo v ∈ V e dato k ∈ {0, . . . , n − 1},
diciamo che P è un cammino k-ristretto se ha al massimo k archi. Per ogni v ∈ V , indichiamo
con ℓk (v) il costo di un cammino k-ristretto da s a v di costo minimo, dove ℓk (v) = +∞ se
non esiste alcun cammino k-ristretto da s a v.
Proposizione 6.5 Sia D = (V, A) un grafo orientato, siano s, v ∈ V e siano (ce )e∈A costi
tali che D non contenga alcun ciclo di costo negativo. Sia P un cammino k-ristretto di costo
67
6.2. L’ALGORITMO DI BELLMAN–FORD
1
∞
s
s
s
s∞
s
0
s
0
s∞
s
s∞
s
s
∞
∞
s
∞
1
s
s
∞
∞
1
1
s
s
s∞
s
s
s2
s
0
s
0
s2
s
s2
s
s
∞
1
s2
s
s
3
1
s
s
2
2
Figura 6.1: Esempio di applicazione dell’algoritmo per il calcolo dei cammini di lunghezza
minima dal nodo s. La prima figura rappresenta l’inizializzazione (passo 1), mentre ciacuna
delle figure successive corrisponde ad un’iterazione del ciclo 2. In ciascuna di queste: i numeri
accanto ai nodi rappresentano le distanze d(v) calcolate dall’algoritmo; i nodi in R sono
precisamente quelli con un’etichetta diversa da +∞; gli archi in grassetto vanno da p(v) a v
per ogni v ∈ R ∖ {s}. Si noti che all’ultima iterazione la scelta dell’arco da aggiungere non è
univoca: avremmo potuto scegliere l’arco verticale più a destra. Si osservi anche che dopo la
terza iterazione è possibile arrestare l’esecuzione dell’algoritmo, in quanto R = V . Al termine
dell’algoritmo, gli archi in grassetto costituiscono un albero di cammini di lunghezza minima.
minimo da s a v, per qualche k ∈ {1, . . . , n − 1}. Se u è il predecessore di v in P , allora Psu è
un cammino (k − 1)-ristretto di costo minimo da s e u.
Dimostrazione. Supponiamo per assurdo che esista un cammino (k − 1)-ristretto Q da s a
u tale che c(Q) < c(Psu ). Se Q non contiene v, allora la sequenza Q, (u, v), v definisce un
cammino k-ristretto da s a v il cui costo è c(Q) + cuv < c(Psu ) + cuv = c(P ), in contraddizione
con il fatto che P è un cammino k-ristretto di costo minimo da s a v. Supponiamo dunque che
Q contenga v. Allora la sequenza Qvu , (u, v), v definisce un ciclo C. Poiché P è un cammino
k-ristretto di costo minimo da s a v e Qsv ha meno di k archi, c(P ) ≤ c(Qsv ). Dunque
c(C) = c(Qvu ) + cuv = (c(Q) − c(Qsv )) + (c(P ) − c(Psu ))
= (c(Q) − c(Psu )) + (c(P ) − c(Qsv )) < 0 + 0.
Pertanto C è un ciclo di costo negativo, contro le ipotesi.
◻
La proposizione precedente implica che se D non contiene cicli di costo negativo, allora,
per ogni v ∈ V e k ∈ {1, . . . , n − 1},
ℓk (v) =
min {ℓk−1 (u) + cuv }.
u∶(u,v)∈A
(6.2)
68
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
Osserviamo inoltre che vale l’ovvia proprietà ℓk (v) ≤ ℓk−1 (v).
Il seguente risultato rispecchia in qualche modo il Lemma 6.2 per il caso dei cammini di
lunghezza minima.
Lemma 6.6 Sia D = (V, A) un grafo orientato, siano s, v ∈ V e siano (ce )e∈A costi tali che D
non contenga alcun ciclo di costo negativo. Fissato k ∈ {1, . . . , n − 1}, supponiamo che ℓk (v) <
ℓk−1 (v). Sia u un nodo che realizza il minimo in (6.2), cioè (u, v) ∈ A e ℓk (v) = ℓk−1 (u) + cuv .
Allora, se Q è un qualunque cammino (k − 1)-ristretto di costo minimo da s a u, la sequenza
Q, (u, v), v costituisce un cammino k-ristretto di costo minimo da s a v.
Dimostrazione. Se Q non contiene v, allora la sequenza Q, (u, v), v costituisce un cammino
k-ristretto da s a v il cui costo è c(Q) + cuv = ℓk−1 (u) + cuv = ℓk (v), quindi è un cammino
k-ristretto di costo minimo da s a v. Basta dunque dimostrare che Q effettivamente non
contiene v. Se Q contenesse v, allora la sequenza Qvu , (u, v), v definirebbe un ciclo C, il cui
costo sarebbe
c(C) = c(Qvu ) + cuv = (c(Q) − c(Qsv )) + (ℓk (v) − ℓk−1 (u))
= (c(Q) − ℓk−1 (u)) + (ℓk (v) − c(Qsv )) < 0 + (ℓk−1 (v) − c(Qsv )) ≤ 0,
una contraddizione.
◻
Si tenga presente che il risultato del lemma precedente potrebbe non valere se ℓk (v) =
ℓk−1 (v). (Tuttavia, si vede subito dalla dimostrazione che se D non contenesse né cicli di costo
negativo né cicli di costo nullo allora il risultato varrebbe anche quando ℓk (v) = ℓk−1 (v).)
Diamo ora una descrizione formale dell’algoritmo di Bellman–Ford. Come nel caso dell’algoritmo per cammini di lunghezza minima visto in precedenza, manterremo un predecessore
p(v) di ogni nodo v ∈ V ∖ {s}, dove p(v) sarà il nodo che precede v in un cammino di costo
minimo da s a v. Grazie al Lemma 6.6, tramite i p(v) sarà possibile ricostruire tutti i cammini
minimi con sorgente s.
Algoritmo di Bellman–Ford
Input: Un grafo orientato D = (V, A), un nodo s ∈ V e costi (ce )e∈A tali che
D non contenga cicli di costo negativo.
Output: Per ogni v ∈ V , il costo ℓ(v) di un cammino minimo da s a v ed un
nodo p(v) che precede v in un tale cammino (se ℓ(v) < +∞; altrimenti p(v)
non è definito).
1. Si ponga ℓ0 (s) ∶= 0 e ℓ0 (v) ∶= +∞ per ogni v ∈ V ∖ {s};
2. Per k = 1, . . . , ∣V ∣ − 1:
2.1 Si ponga ℓk (v) ∶= ℓk−1 (v) per ogni v ∈ V ;
2.2 Per ogni (u, v) ∈ A:
Se ℓk (v) > ℓk−1 (u) + cuv ,
si aggiornino ℓk (v) ∶= ℓk−1 (u) + cuv e p(v) ∶= u;
3. Si ponga ℓ(v) ∶= ℓ∣V ∣−1 (v) per ogni v ∈ V .
69
6.2. L’ALGORITMO DI BELLMAN–FORD
Teorema 6.7 L’algoritmo di Bellman–Ford funziona correttamente ed esegue O(∣V ∣ ⋅ ∣A∣)
operazioni.
Dimostrazione. Per dimostrare la correttezza dell’algoritmo, è sufficiente osservare che, alla
fine della k-esima iterazione, ℓk (v) è esattamente il costo minimo di un cammino k-ristretto
da s a v. Questo può essere facilmente dimostrato per induzione, osservando che la proprietà
è chiaramente vera per ℓ0 dopo l’esecuzione del passo 1 e che si mantiene vera grazie alla
formula (6.2). Poiché ogni cammino in D ha al massimo ∣V ∣ − 1 archi, l’istruzione 3 definisce
ℓ(v) come il costo minimo tra tutti i cammini da s a v.
Per verificare che i puntatori p(v) permettono di ricostruire a ritroso i cammini minimi,
osserviamo che se ℓk (v) = ℓk−1 (v), allora la k-esima iterazione dell’algoritmo non aggiorna
il predecessore p(v) (e questa scelta è corretta, perché il cammino (k − 1)-ristretto di costo
minimo già trovato è anche un cammino k-ristretto di costo minimo); in caso contrario, detto
u un nodo che realizza il minimo in (6.2), l’algoritmo pone p(v) = u. Il Lemma 6.6 garantisce
che questa scelta è corretta.
Per quanto riguarda la complessità, l’algoritmo compie ∣V ∣ − 1 iterazioni, una per ogni
valore di k. Ad ogni iterazione, vengono considerati uno alla volta tutti gli archi e, per
ciascuno di essi, viene eseguito un confronto ed eventualmente un aggiornamento di ℓk (v) e
p(v). Nel complesso, vengono svolte O(∣V ∣ ⋅ ∣A∣) operazioni.
◻
Sia R l’insieme dei nodi raggiungibili da s. Al termine dell’esecuzione dell’algoritmo di
Bellman–Ford, ℓ(v) < +∞ esattamente per i nodi v ∈ R; inoltre, per tali nodi p(v) è definito.
Come nel caso del problema dei cammini di lunghezza minima, il sottografo T = (R, A′ ), dove
A′ = {(p(v), v) ∶ v ∈ R ∖ {s}}, è un’arborescenza con radice s. Per ogni v ∈ R, l’unico cammino
da s a v in T è un cammino di costo minimo da s a v in D. Si dice che T è un albero di
cammini di costo minimo.
Esempio 6.8 Risolvere il problema dei cammini minimi a partire dal nodo s nel grafo in
figura (i numeri vicino agli archi ne indicano il costo).
a
d
1
3
−2
s
−2
c
3
1
−1
2
2
b
2
e
Il grafo assegnato non contiene alcun ciclo di costo negativo: per il momento dobbiamo
accontentarci di una verifica “a occhio”, ma vedremo nella Sezione 6.4 come controllare questa proprietà in modo efficiente. Possiamo dunque applicare l’algoritmo di Bellman–Ford.
Rappresentiamo le iterazioni dell’algoritmo attraverso una tabella, con una riga per ogni iterazione e una colonna per ogni nodo. Nella posizione situata nella riga relativa all’iterazione k
e nella colonna relativa al nodo v, scriviamo il valore di ℓk (v) ed il nodo p(v) ottenuti alla fine
della k-esima iterazione. La riga k = 0 corrisponde all’inizializzazione (passo 1 dell’algoritmo).
Poiché il grafo ha sei nodi, l’algoritmo eseguirà cinque iterazioni.
70
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
Iterazione
k=0
k=1
k=2
k=3
k=4
k=5
s
0
0
0
0
0
0
(−)
(−)
(−)
(−)
(−)
(−)
a
b
c
d
e
∞ (−)
3 (s)
3 (s)
3 (s)
3 (s)
3 (s)
∞ (−)
2 (s)
1 (a)
1 (a)
1 (a)
1 (a)
∞ (−)
∞ (−)
1 (a)
1 (a)
1 (a)
1 (a)
∞ (−)
∞ (−)
∞ (−)
2 (c)
2 (c)
2 (c)
∞ (−)
∞ (−)
4 (b)
3 (b)
1 (d)
1 (d)
Come si evince dalla dimostrazione del Teorema 6.7, alla fine della k-esima iterazione la
tabella fornisce i cammini minimi k-ristretti. Si noti che dopo l’iterazione k = 4 non viene
fatto alcun aggiornamento. Questo vuol dire che tutti i cammini di costo minimo trovati
hanno lunghezza inferiore o uguale a 4. L’ultima riga della tabella permette di costruire un
albero di cammini di costo minimo, come rappresentato in figura.
a
d
1
3
−2
s
−2
c
3
1
−1
2
2
b
6.3
2
e
Cammini minimi e programmazione lineare
In questa sezione mostriamo come la programmazione lineare possa essere utilizzata per
risolvere il problema di trovare il cammino di costo minimo tra due nodi fissati.
Sarà utile il seguente risultato. Ricordiamo che indichiamo con ℓ(v) il costo di un cammino
minimo da s a v.
Proposizione 6.9 Sia D = (V, A) un grafo orientato con costi (ce )e∈A tali che D non contenga alcun ciclo orientato di costo negativo. Fissiamo s ∈ V tale che ogni nodo v ∈ V sia
raggiungibile da s. Allora valgono le condizioni di Bellman:
ℓ(v) − ℓ(u) ≤ cuv
per ogni (u, v) ∈ A.
(6.3)
Dimostrazione. Supponiamo per assurdo che esista un arco (u, v) ∈ A tale che ℓ(v)−ℓ(u) > cuv .
Sia P un cammino di costo minimo da s a u. Se v ∉ V (P ), allora la sequenza P, (u, v), v
forma un cammino da s a v di costo ℓ(u) + cuv < ℓ(v), una contraddizione. Dunque possiamo
assumere che v sia un nodo di P . Si noti che il sottocammino Psv ha costo almeno ℓ(v).
Allora la sequenza Pvu , (u, v), v definisce un ciclo C in D per cui vale
c(C) = c(P ) − c(Psv ) + cuv ≤ ℓ(u) − ℓ(v) + cuv < 0,
una contraddizione.
◻
Fissiamo un grafo orientato D = (V, A) con costi (ce )e∈A sugli archi e due nodi s, t ∈ V .
Definiamo una variabile xe per ogni arco e ∈ A. Ad ogni cammino P da s a t associamo il
vettore x ∈ RA cosı̀ definito:
⎧
⎪
⎪1 se e ∈ A(P )
xe = ⎨
(6.4)
⎪0 altrimenti.
⎪
⎩
6.3. CAMMINI MINIMI E PROGRAMMAZIONE LINEARE
71
Ogni cammino P da s a t contiene esattamente un arco di δ+ (s) e nessun arco di δ− (s),
mentre contiene esattamente un arco di δ− (t) e nessun arco di δ+ (t); inoltre, per ogni vertice
v ∈ V (P ) ∖ {s, t}, P contiene esattamente un arco di δ+ (v) e un arco di δ− (v); infine, per ogni
v ∈ V ∖ V (P ), P non contiene archi né di δ+ (v) né di δ− (v). Allora, affinché x ∈ RA sia un
vettore corrispondente ad un cammino da s a t, dovranno essere soddisfatti i vincoli
⎧
−1 se v = s
⎪
⎪
⎪
⎪
∑ xe − ∑ xe = ⎨+1 se v = t
⎪
⎪
e∈δ− (v)
e∈δ+ (v)
⎪
⎪
⎩ 0 se v ∈ V ∖ {s, t}.
(6.5)
Inoltre, dovrà valere xe ≥ 0 per ogni e ∈ A.
Se x è associato ad un cammino P da s a t, il costo di P può essere espresso come
∑e∈A ce xe . Consideriamo dunque il seguente programma lineare:
min ∑ ce xe
(6.6)
e∈A
s.a
∑ xe − ∑ xe = bv , v ∈ V,
e∈δ− (v)
(6.7)
e∈δ+ (v)
xe ≥ 0,
e ∈ A,
(6.8)
dove, per scrivere le condizioni (6.5) in modo compatto, abbiamo posto bs = −1, bt = 1 e bv = 0
per ogni v ∈ V ∖ {s, t}. La discussione precedente implica la seguente osservazione.
Osservazione 6.10 Sia P un cammino da s a t in D e sia x il corrispondente vettore definito
come in (6.4). Allora x è una soluzione ammissibile per il programma lineare (6.6)–(6.8) e
ha costo c(P ).
Il viceversa non è vero: ci sono soluzioni ammissibili di (6.6)–(6.8) le cui componenti sono
diverse da 0 e 1 e che dunque non sono associate ad alcun cammino. Vedremo, tuttavia, che il
valore ottimo del programma lineare (6.6)–(6.8) è esattamente il costo di un cammino minimo
da s a t quando il grafo non contiene cicli di costo negativo.
Scriviamo il duale di (6.6)–(6.8). Poiché c’è una variabile per ogni arco ed un vincolo
per ogni nodo del grafo, il duale avrà un vincolo per ogni arco e una variabile yv per ogni
nodo v ∈ V . Per ogni arco (u, v) ∈ A, la variabile xuv ha coefficiente +1 nel vincolo (6.7)
corrispondente al nodo v e coefficiente −1 nel vincolo corrispondente al nodo u, mentre ha
coefficiente 0 in tutti gli altri vincoli (6.7). Pertanto, il duale è il seguente:
max yt − ys
s.a yv − yu ≤ cuv ,
(6.9)
(u, v) ∈ A.
(6.10)
Si noti che i vincoli del problema duale dipendono unicamente da D e da c, ma non dalla scelta
dei nodi s e t. Una soluzione ammissibile y del duale è chiamata un potenziale ammissibile
per (D, c).
Teorema 6.11 Sia D = (V, A) un grafo orientato con costi (ce )e∈A sugli archi. Esiste un
potenziale ammissibile per (D, c) se e solo se D non contiene alcun ciclo di costo negativo
rispetto a c.
72
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
Dimostrazione. Per il Lemma di Farkas (Teorema 4.13), esiste un potenziale ammissibile per
(D, c) se e solo se non esiste alcun x ∈ RA tale che
∑ ce xe
<0
(6.11)
e∈A
∑ xe − ∑ xe = 0, v ∈ V
e∈δ− (v)
(6.12)
e∈δ+ (v)
xe ≥ 0, e ∈ A.
(6.13)
Basta dunque dimostrare che D contiene un ciclo di costo negativo se e solo il sistema (6.11)–
(6.13) ha una soluzione.
Supponiamo che D contenga un ciclo di costo negativo C e sia x̄ il vettore definito da
⎧
⎪
⎪1 se e ∈ A(C)
x̄e = ⎨
⎪
⎪
⎩0 altrimenti.
(6.14)
Allora x̄ è una soluzione del sistema (6.11)–(6.13).
Viceversa, supponiamo che il sistema abbia una soluzione x̄ e scegliamo x̄ col numero
minimo di componenti positive. Definiamo S = {e ∈ A ∶ x̄e > 0}. Si noti che S ≠ ∅, altrimenti
x̄ non soddisferebbe la disequazione (6.11). Allora esiste un arco (v0 , v1 ) ∈ S. Siccome
∑e∈δ− (v1 ) x̄e > 0, per la (6.12) esiste (v1 , v2 ) ∈ S. Procedendo in questo modo, costruiamo una
sequenza di archi (v0 , v1 ), (v1 , v2 ), (v2 , v3 ), . . . tutti in S. Poiché il numero di nodi è finito,
prima o poi troveremo un nodo vk già visitato, cioè chiuderemo un ciclo C tale che A(C) ⊆ S.
Sia ε = min{x̄e ∶ e ∈ A(C)}. Se definiamo un vettore x′ ponendo
⎧
⎪
⎪x̄e − ε
x′e = ⎨
⎪
⎪
⎩x̄e
se e ∈ A(C)
altrimenti
(6.15)
si verifica facilmente che x′ soddisfa le condizioni (6.12)–(6.13). Se c(C) < 0, abbiamo trovato
il ciclo che cercavamo; dunque assumiamo c(C) ≥ 0. Allora
∑ ce xe = ∑ ce x̄e − ε ⋅ c(C) ≤ ∑ ce x̄e < 0,
′
e∈A
e∈A
e∈A
dunque x′ soddisfa tutte le condizioni (6.11)–(6.13). Per come è stato definito ε, x′ ha meno
componenti positive di x̄, il che contraddice la scelta di x̄.
◻
Il Teorema 6.13, enunciato più sotto, mostra che in assenza di cicli di costo negativo il
programma lineare (6.6)–(6.8) permette di determinare il costo di un cammino minimo da s
a t. Prima però dobbiamo dimostrare un lemma preliminare.
Lemma 6.12 Sia D = (V, A) un grafo orientato con costi (ce )e∈A sugli archi e siano s, t ∈
V due nodi fissati. Sia R l’insieme dei nodi raggiungibili da s e supponiamo che t ∈ R.
Se il programma lineare (6.6)–(6.8) associato a D ha una soluzione ottima, allora anche il
programma lineare (6.6)–(6.8) associato al sottografo di D indotto da R ha una soluzione
ottima; inoltre, i valori ottimi coincidono.
Dimostrazione. Indichiamo con D ′ il sottografo di D indotto da R e con A′ l’insieme degli
archi di D ′ ; dunque D ′ = (R, A′ ). Chiamiamo PL(D) e PL(D ′ ) i programmi lineari della
6.3. CAMMINI MINIMI E PROGRAMMAZIONE LINEARE
73
forma (6.6)–(6.8) associati rispettivamente a D e D ′ . PL(D ′ ) si ottiene da PL(D) ponendo
a zero le variabili xe con e ∈ A ∖ A′ . Dunque PL(D ′ ) è più vincolato di PL(D). Allora
sarà sufficiente dimostrare che ogni soluzione ammissibile PL(D) (in particolare la soluzione
ottima) è ammissibile anche per PL(D ′ ).
Sia x̄ ∈ RA una soluzione ammissibile PL(D). Definiamo
δ+ (R) = {(u, v) ∈ A ∶ u ∈ R, v ∉ R},
δ− (R) = {(u, v) ∈ A ∶ u ∉ R, v ∈ R}.
Consideriamo la somma delle equazioni (6.7) di PL(D) corrispondenti ai nodi v ∈ R. Se e è
un arco con entrambi gli estremi in R, la variabile xe appare una volta con coefficiente +1 e
una volta con coefficiente −1; dunque queste variabili non appaiono nella somma. Otteniamo
allora
(6.16)
∑ x̄e − ∑ x̄e = 0
e∈δ− (R)
e∈δ+ (R)
(il termine noto è 0 perché s, t ∈ R). Si noti che δ+ (R) = ∅: infatti, se esistesse (u, v) ∈ A
con u ∈ R e v ∉ R, allora, poiché u è raggiungibile da s, anche v lo sarebbe, contraddicendo
il fatto che v ∉ R. Allora, per la (6.16), ∑e∈δ− (R) x̄e = 0, da cui, poiché x̄ ≥ 0, si deduce che
x̄e = 0 per ogni e ∈ δ− (R). Poiché A ∖ A′ = δ+ (R) ∪ δ− (R), abbiamo mostrato che x̄e = 0 per
ogni e ∈ A ∖ A′ , da cui segue che x̄ è ammissibile per PL(D ′ ).
◻
Teorema 6.13 Sia D = (V, A) un grafo orientato con costi (ce )e∈A sugli archi e siano s, t ∈ V
due nodi fissati. Allora:
(i) il programma lineare (6.6)–(6.8) è inammissibile se e solo se non esiste alcun cammino
da s a t in D;
(ii) se il programma lineare (6.6)–(6.8) è illimitato, D ha un ciclo di costo negativo;
(iii) se il programma lineare (6.6)–(6.8) ha soluzione ottima, il valore ottimo è il costo di un
cammino minimo da s a t in D.
Dimostrazione. Mostriamo prima la (i). Supponiamo che (6.6)–(6.8) abbia una soluzione
ammissibile x̄ e scegliamo x̄ col numero minimo di componenti positive. Sia S = {e ∈ A ∶ x̄e >
0}. Supponiamo prima che S contenga un ciclo C e sia ε = min{x̄e ∶ e ∈ A(C)}. Il vettore x′
definito come in (6.15) è ammissibile e ha meno componenti positive di x̄, una contraddizione.
Dunque S non contiene cicli. Ora, l’equazione (6.12) per v = s garantisce l’esistenza di un
arco (s, v1 ) ∈ S. Se v1 ≠ t, l’equazione (6.12) per v = v1 garantisce l’esistenza di un arco
(v1 , v2 ) ∈ S. Poiché S non contiene cicli, v2 ≠ s. Se v2 ≠ t, troviamo (v2 , v3 ) ∈ S e cosı̀ via.
Siccome S non contiene cicli, il processo termina solo quando si trova un nodo vk = t: ma
allora abbiamo individuato un cammino da s a t in S, e dunque in D. Questo mostra che se
(6.6)–(6.8) ha soluzioni ammissibili allora esiste un cammino da s a t in D. Il viceversa segue
dall’Osservazione 6.10.
Per dimostrare la (ii), ricordiamo che se (6.6)–(6.8) è illimitato allora il suo duale (6.9)–
(6.10) è inammissibile. Questo significa che non ci sono potenziali ammissibili in (D, c). Per
il Teorema 6.11, D ha un ciclo di costo negativo.
Dimostriamo ora la (iii). Sia x̄ una soluzione ottima del programma lineare. Per il Lemma 6.12, possiamo restringerci al sottografo indotto da R; per non appesantire la notazione,
74
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
assumiamo senza perdita di generalità che R = V , dunque lavoriamo direttamente sul programma lineare originale. Poiché R = V , per ogni v ∈ V il costo di un cammino minimo da s a
v è un valore ℓ(v) < +∞. Definiamo ȳv = ℓ(v). Poiché valgono le condizioni di Bellman (6.3),
ȳ è una soluzione ammissibile del problema duale (6.9)–(6.10). Inoltre, poiché ȳs = ℓ(s) = 0, il
costo di ȳ è pari a ℓ(t). Allora, per il Teorema di Dualità Debole, il costo di x̄ è almeno ℓ(t),
cioè il valore ottimo del programma lineare è almeno ℓ(t). D’altro canto, detto P un cammino minimo da s a t (dunque c(P ) = ℓ(t)), sappiamo dall’Osservazione 6.10 che il programma
lineare ha una soluzione ammissibile di costo ℓ(t). Dunque il valore ottimo del programma
lineare è proprio ℓ(t).
◻
In particolare, se D non ha cicli di costo negativo, allora il programma lineare (6.6)–(6.8)
permette di determinare il costo di un cammino minimo da s a t. Si noti però che conoscere
il costo di un cammino minimo non significa conoscere anche un cammino minimo. Tuttavia,
è possibile dimostrare (ma non lo facciamo), che se x∗ è una soluzione ottima di base, allora
tutte le componenti di x∗ sono 0 o 1, dunque x∗ corrisponde ad un cammino minimo P
secondo la relazione (6.4). Poiché il metodo del simplesso restituisce sempre una soluzione
ottima di base (quando il problema ammette ottimo), tale metodo può essere usato per trovare
un cammino di costo minimo (in assenza di cicli di costo negativo).
Osserviamo anche che nella parte (iii) abbiamo dimsotrato che il vettore (ℓ(v))v∈V costituisce una soluzione ottima del programma lineare duale.
Notiamo infine che l’inversa della proposizione (ii) del Teorema 6.13 non vale: infatti, è
facile costruire un grafo orientato D che abbia cicli di costo negativo ma che sia privo di
cammini da s a t; in tal caso, il programma lineare è inammissibile.
6.4
Trovare cicli di costo negativo
Concludiamo il capitolo mostrando come risolvere il seguente problema: dato un grafo orientato D = (V, A) con costi (ce )e∈A sugli archi, si trovi un ciclo di costo negativo in D o si
stabilisca che un tale ciclo non esiste. Questa informazione è necessaria, ad esempio, per
sapere se è lecito applicare l’algoritmo di Bellman–Ford (ma ci sono anche altre applicazioni).
Costruiamo un grafo orientato D ′ = (V ′ , A′ ) con V ′ = V ∪ {s}, dove s è un nuovo nodo
(dunque s ∉ V ), e A′ = A ∪ {(s, v) ∶ v ∈ V }. In altre parole, dal nuovo nodo escono archi verso
tutti gli altri nodi del grafo. Definiamo costi (c′e )e∈A′ sugli archi di D ′ ponendo
⎧
⎪
⎪ce
c′e = ⎨
⎪
⎪
⎩0
per e ∈ A
per e ∈ A′ ∖ A.
Applichiamo al nuovo grafo l’algoritmo di Bellman–Ford rispetto al nodo sorgente s (questo
è possibile anche se non sappiamo se il grafo contenga cicli di costo negativo, in quanto le
istruzioni dell’algoritmo sono comunque ben poste). L’algoritmo restituisce valori ℓ(v) per
ogni v ∈ V ′ . Si tenga presente che ℓ(v) non è necessariamente il costo di un cammino minimo
da s a v, in quanto potremmo aver applicato l’algoritmo ad un grafo contenente cicli di
costo negativo (ed in tal caso l’algoritmo non trova correttamente i cammini minimi). Come
vedremo, se consideriamo le sole componenti ℓ(v) associate ai nodi originali del grafo (dunque
solo per v ≠ s), D non ha cicli di costo negativo se e solo se il vettore (ℓ(v))v∈V soddisfa le
condizioni di Bellman (6.3). Otteniamo dunque il seguente algoritmo.
6.4. TROVARE CICLI DI COSTO NEGATIVO
75
Algoritmo per trovare cicli di costo negativo
Input: Un grafo orientato D = (V, A) con costi (ce )e∈A sugli archi.
Output: L’informazione se c’è un ciclo di costo negativo in D.
1. Si definisca V ′ ∶= V ∪ {s}, A′ ∶= A ∪ {(s, v) ∶ v ∈ V }, D ′ ∶= (V ′ , A′ ), c′e ∶= ce per ogni
e ∈ A, c′e ∶= 0 per ogni e ∈ A′ ∖ A.
2. Si applichi l’algoritmo di Bellman–Ford a D ′ .
3. Se il vettore (ℓ(v))v∈V restituito dall’algoritmo di Bellman–Ford soddisfa le condizioni di Bellman (6.3), allora D non ha cicli di costo negativo; altrimenti, D ha un
ciclo di costo negativo.
Teorema 6.14 L’algoritmo per trovare cicli di costo negativo funziona correttamente ed
esegue O(∣V ∣ ⋅ ∣A∣) operazioni.
Dimostrazione. Se il vettore (ℓ(v))v∈V soddisfa le condizioni di Bellman (6.3), allora (ℓ(v))v∈V
è un potenziale ammissibile per (D, c) e dunque D non contiene alcun ciclo di costo negativo
(Teorema 6.11).
Mostriamo ora che se invece le condizioni di Bellman non sono soddisfatte da (ℓ(v))v∈V ,
allora D contiene un ciclo di costo negativo. Se, per assurdo, D non contenesse alcun ciclo di
costo negativo rispetto a c, allora D ′ non conterrebbe alcun ciclo di costo negativo rispetto
a c′ (perché nessun ciclo di D ′ può contenere il nodo s). Allora, poiché in questo caso
l’algoritmo di Bellman–Ford funziona correttamente (Teorema 6.7), avremmo che ℓ(v) è il
costo di un cammino minimo da s a v in D ′ . Poiché tutti i nodi di D ′ sono raggiungibili da
s, la Proposizione 6.9 implica che valgono le condizioni di Bellman, contro l’ipotesi.
Per quanto riguarda la stima sul numero di operazioni, osserviamo che ∣V ′ ∣ = ∣V ∣ + 1 e
′
∣A ∣ = ∣A∣ + ∣V ∣. Allora, per il Teorema 6.7, il passo 2 richiede O(∣V ′ ∣ ⋅ ∣A′ ∣) = O(∣V ∣ ⋅ ∣A∣)
operazioni. I passi 1 e 3 richiedono un numero di operazioni inferiore a O(∣V ∣ ⋅ ∣A∣).
◻
Se l’algoritmo determina che esiste un ciclo di costo negativo, tale ciclo può essere esplicitamente costruito. Infatti, supponiamo che il vettore (ℓ(v))v∈V trovato al punto 2 non soddisfi
le condizioni di Bellman e sia (u, v) ∈ A tale che ℓ(v) − ℓ(u) > cuv . Si consideri la sequenza
di nodi v, u, p(u), p(p(u)), p(p(p(u))), eccetera, dove p(⋅) indica il nodo predecessore determinato dall’algoritmo di Bellman–Ford. Si può dimostrare che questa sequenza visita uno
stesso nodo due volte, senza arrivare al nodo fittizio s; ciò determina un cilco, che ha costo
negativo (omettiamo la dimostrazione).
Esempio 6.15 Determinare se il grafo rappresentato nella parte sinistra della Figura 6.2
contiene un ciclo di costo negativo.
Aggiungiamo al grafo dato un nuovo nodo s, da cui escono archi di costo 0 verso tutti gli
altri nodi. Il grafo cosı̀ ottenuto è rappresentato nella parte destra della Figura 6.2.
Applichiamo l’algoritmo di Bellman–Ford al nuovo grafo rispetto al nodo sorgente s e
compiliamo una tabella come spiegato nell’Esempio 6.8.
76
CAPITOLO 6. PROBLEMI DI CAMMINO MINIMO
as
sd
1
as
−2
1
−2
b
c
ss
−2
s
3
1
−2
c
−2
sd
1
−2
s
2
3
s
s
−3
e
b
2
s
s
−3
e
Figura 6.2: Costruzione del grafo D ′ . Gli archi tratteggiati hanno costo 0.
Iterazione
k=0
k=1
k=2
k=3
k=4
k=5
s
0
0
0
0
0
0
(−)
(−)
(−)
(−)
(−)
(−)
a
b
c
d
e
∞ (−)
0 (s)
0 (s)
0 (s)
0 (s)
0 (s)
∞ (−)
0 (s)
−2 (a)
−2 (a)
−2 (a)
−2 (a)
∞ (−)
0 (s)
−2 (a)
−2 (a)
−3 (e)
−3 (e)
∞ (−)
0 (s)
0 (s)
−1 (c)
−1 (c)
−2 (c)
∞ (−)
0 (s)
−3 (b)
−5 (b)
−5 (b)
−5 (b)
Le condizioni di Bellman sono violate per l’arco (d, a): infatti, ℓ(a) = 0, ℓ(d) = −2, cda = 1,
ma 0 − (−2) = 2 > 1. Dunque esiste un ciclo di costo negativo. Il ciclo può essere determinato
seguendo a ritroso la lista dei predecessori a partire dall’arco (d, a), fino a quando si visita
due volte uno stesso nodo: a, d, p(d) = c, p(c) = e, p(e) = b, p(b) = a. Questo determina il
ciclo che visita i nodi a, b, e, c, d, a, che in effetti ha costo −1.