Sei sulla pagina 1di 11

Capitolo 5

Alberi ricoprenti di costo minimo

5.1 Definizioni fondamentali


Sia dato un grafo non orientato G(N, A) connesso, con costi associati agli archi cuv per ogni

uv ∈ A. Dato un insieme di archi F ⊆ A si definisce costo di F la quantità c(F ) = uv∈F cuv .
Dato un albero ricoprente H = (N, T ) di G, definiamo costo di H la quantità c(H) = c(T ),
cioè il costo degli archi di H. Il problema dell’albero minimo consiste nel trovare un albero
ricoprente H = (N, T ∗ ) di costo c(H) minimo. In figura 5.1 è mostrato un grafo e un suo albero
ricoprente di costo minimo (c(T ∗ ) = 18). Talvolta un albero ricoprente H(N, T ) viene indicato
semplicemnte con T (l’insieme dei suoi archi).
2 2
cab=3 b 10 3 b 10
c c
a 6 g a 6 g
1 1
1 8 1 8
d d
f f
3 e 3 e
7 7
Figura 5.1: Un albero ricoprente di costo minimo

I prossimi teoremi sono la base degli algoritmi di accrescimento per la costruzione di alberi
ricoprenti di costo minimo.

Teorema 5.1.1 (Teorema del ciclo fondamentale) Sia H(N, T ) un albero ricoprente di G(N, A).
Per ogni arco f ∈ A − T il grafo H(N, T ∪ {f }) contiene un solo ciclo C.

Dim. Ogni ciclo di H(N, T ∪ {f }) deve contenere l’arco f . Infatti, se così non fosse, allora il
ciclo sarebbe contenuto in H(N, T ), contraddicendo il fatto che H è un albero. Siano ora u e v
gli estremi di f . Ogni ciclo di H(N, T ∪ {f }) corrisponde a un cammino P fra u e v in H(N, T )
(vedi Figura 5.2).

45
u f
P
v
f

H(N,T)
Figura 5.2: Ciclo associato all’arco f

Ma per il Teorema 2.4.1 esiste un solo cammino P fra u e v in H(N, T ) e quindi esiste un
solo ciclo in H(N, T ∪ {f }).
L’unico ciclo del grafo H(N, T ∪ {f }) è detto ciclo fondamentale C(f, T ) di G(N, A).

Teorema 5.1.2 (Teorema dello scambio) Sia T un albero ricoprente di G(N, A), sia f ∈ A − T
e sia g = uv un arco di C(f, T ). Allora, H(N, T ∪ {f } − {g}) è un albero ricoprente di G.

u u
C(T,f) g
g v
v
P P
f f

Figura 5.3: Scambio dell’arco g e dell’arco f

Dim. Il grafo H(N, T ∪ {f }) contiene un solo ciclo C(f, T ). Quindi, rimuovendo l’arco
g ∈ C(f, T ), il ciclo stesso viene ”distrutto” e il grafo H(N, T ∪ {f } − {g}) è aciclico. Mostriamo
ora che è connesso. Si osservi innanzi tutto che gli estremi di g, u e v, sono connessi in H(N, T ∪
{f } − {g}). Infatti u e v apparetengono a C(f, T ) e rimuovendo un solo arco dal ciclo i nodi del
ciclo restano connessi. Supponiamo ora per assurdo che H(N, T ∪ {f } − {g}) non sia connesso.
Allora esistono due nodi z e w che non sono connessi in H(N, T ∪ {f } − {g}) ma sono connessi
in H(N, T ) (perché ogni coppia di nodi è connessa in H(N, T )). Questo implica che l’arco g si
trovava sul cammino (unico) che connetteva z e w in H(N, T ).
Ma allora, senza perdita di generalità, possiamo supporre che in H(N, T ∪ {f } − {g}), z
sia connesso a u e w sia connesso a v. Ma allora, per la proprietà transitiva della relazione di
connessione, essendo u e v connessi, si ha che u è connesso a w e dunque z è connesso a w,
contraddizione.
Essendo H(N, T ∪ {f } − {g}) un grafo aciclico e connesso, è un albero ricoprente di G.

46
z u g v w

Figura 5.4:

a g a g a g
e h e h e
b m q b m q h
d d b m q
d
c l c l c
f f f l
T0 T1 T2

Figura 5.5: Sequenza di alberi ricoprenti con scambi su cicli fondamentali

In Figura 5.5 sono mostrati tre alberi ricoprenti; gli ultimi due sono ottenuti per scambi
successivi di archi su cicli fondamentali. Specificamente, chiamando T0 , T1 e T2 i tre alberi, T1
è ottenuto da T0 scambiando, sul ciclo fondamentale C1 = {a, d, f, q, g}, l’arco g con l’arco f ;
mentre T2 è ottenuto da T1 scambiando, sul ciclo fondamentale C2 = {b, c, d}, l’arco d con l’arco
c.

5.2 Teoremi di accrescimento


In questo paragrafo enunceremo e dimostreremo una serie di teoremi che sono alla base dei
principali algoritmi per la costruizione di alberi ricoprenti di costo minimo. Questi algoritmi
costruiscono l’albero iterativamente, accrescendo una foresta aggiungendo un nuovo arco a ogni
iterazione, finché il numero di archi è pari al numero di nodi - 1.

Definizione 5.2.1 Una foresta H(S, T ) di G(N, A) è parzialmente ottima (rispetto a c) se esiste
un albero ricoprente T ∗ di costo minimo (rispetto a c) di G(N, A) che contiene T (T ∗ ⊇ T ).

Nel grafo in Figura 5.6 è mostrato il grafo G(N, A) con N = {a, b, d, e, g, w, y}; un al-
bero ricoprente ottimo (non mostrato in figura) è H(N, T ) con T = {bw, dw, ad, wy, gy, ey}.
E’ facile vedere che l’albero non ricoprente H1 = ({a, b, d, w, y}, {bw, ad, dw, wy}) mostrato
in figura è un albero parzialmente ottimo, mentre si può far vedere che la foresta H0 =
({a, b, d, w, e, y}, {ab, ad, dw, ey}).

47
2 2 w
w
3 3 b
b
10 10
a a g
S g S
1 1 1 1
1 1 3
3
d d
y y
H0 3 H1 3 e
e
2 2

Figura 5.6: foresta e albero parziali

Proprietá 5.2.1 Un albero ricoprente H(N, T ) di G(N, A) parzialmente ottimo è un albero


ricoprente di costo minimo di G(N, A).

Dim. Poiché H(N, T ) è parzialmente ottimo esiste un albero ricoprente T ∗ di costo minimo
tale che T ⊆ T ∗ . Se T ⊂ T ∗ esiste un arco uv ∈ T ∗ −T . Tuttavia, essendo T un albero ricoprente,
T ∪ {uv} contiene un ciclo. Ma T ∪ {uv} ⊆ T ∗ , e quindi T ∗ contiene un ciclo, contraddizione.
Quindi T = T ∗ e H(N, T ) è un albero ricoprente di costo minimo.

Teorema 5.2.2 (Teorema di accrescimento) Sia H(S, T ) una foresta parzialmente ottima di
G(N, A) e sia M = {uv ∈ A − T tale che H (S ∪ {u, v}, T ∪ {uv}) è una foresta}, cioè M è
l’insieme degli archi che possono essere ”aggiunti” a H senza introdurre cicli. Sia infine wy un
arco a costo minimo di M. Allora H (S ∪ {w, y}, T ∪ {wy}) è una foresta parzialmente ottima.

Dim. Supponiamo per assurdo che H non sia parzialmente ottima. Poiché H(S, T ) è
parzialmente ottima, esiste un albero ricoprente T ∗ di costo minimo tale che T ⊆ T ∗ . Poiché
H non è parzialmente ottima non esiste un albero ricoprente T di costo minimo tale che
T ∪ {wy} ⊆ T , e quindi in particolare T ∪ {wy} ⊆ T ∗ , e quindi wy ∈
/ T ∗ (essendo T ⊆ T ∗ ).

C(wy,T*)
w
w
y
y
T*
T
Figura 5.7: Illustrazione del teorema di accrescimento

Sia C = C(wy, T ∗ ) il ciclo fondamentale indotto da wy in T ∗ . Poiché H è una foresta


(aciclica), il ciclo C non può essere completamente contenute in H e quindi abbiamo che deve
esistere un arco uv ∈ C = C(wy, T ∗ ) che non appartiene a H . Ovvero esiste un arco uv ∈
C(wy, T ∗ ) : uv ∈ / T ∪ {wy}. Quindi uv ∈/ T e uv ∈ T ∗ − T . Ora, uv ∈ A − T e T ∪ {uv} ⊆ T ∗
aciclico, implica che uv ∈ M. Poiché wv ha costo minimo in M, abbiamo che cuv ≥ cwv . Ma

48
allora T ∗∗ = T ∗ − {uv} ∪ {wy} è un albero ricoprente per il Teorema (5.1.2) con c(T ∗∗ ) =
c(T ∗ ) − cuv + cwv ≤ c( T ∗ ) e quindi ha costo minimo. Concludendo T ∗∗ è un albero ricoprente a
costo minimo che soddisfa T ∪ {wy} ⊆ T ∗∗ , contraddicendo l’ipotesi che H (S ∪{w, y}, T ∪ {wy})
non sia una foresta parzialmente ottima.

5.3 L’algoritmo di Kruskal


Il Teorema 5.2.2 è alla base di una semplice strategia proposta da Kruskal nel 1956 per costruire
un albero ricoprente di costo minimo di un grafo G(N, A), accrescendo sequenzialmente una
foresta parzialmente ottima H(S, T ). Cominciando ad esempio con la foresta vuota S = {∅}
T = {∅} (che è contenuta in ogni albero e quindi anche nell’albero di costo minimo), a ogni passo
viene aggiunto a T un arco di A−T di costo minimo che non crei cicli nella nuova foresta parziale.
Quando la foresta H contiene esattamente |N | − 1 archi, grazie al Teorema 2.4.4 (Teorema della
foresta ricoprente), H sarà un albero ricoprente di G.
6
1 v0 v4
v1 12
5
3 v3
7 1
v2
v5

Figura 5.8: Esempio di

L’algoritmo utilizza un vettore ORDINE ove sono memorizzati gli archi di A in ordine di
costo crescente. In ingresso riceve il grafo G(N, A) e restituisce in uscita un albero ricoprente di
costo minimo H(N, T ).
Alla fine T contiene gli archi dell’albero ricoprente di costo minimo.

Complessità dell’algoritmo di Kruskal. Calcoliamo adesso la complessità dell’algoritmo


di Kruskal. Sia m = |A|. L’ordinamento del vettore ORDINE ha costo pari a m log m. Il
blocco repeat viene eseguito al più m volte: infatti, a ogni iterazione viene processato un
arco distinto. Infine, il test di aciclicità richiede O(m) passi (utilizzando, ad esempio, gli al-
goritmi di ricerca visti nel Capitolo 4). Quindi, complessivamente l’agoritmo di Kruskal richiede
O(m log m + m2 ) = O(m2 ) passi.

Vediamo il comportamento dell’algoritmo di Kruskal per il grafo di Figura 5.10. Dopo


l’ordinamento, il vetore ORDINE è quello mostrato in figura. Alla prima iterazione S = T = ∅,
k = 1 e viene quindi analizzato l’arco (1, 2) = ORDINE[1]. Poiché l’inserimento in T dell’arco
(1, 2) non crea cicli in H (S, T ) lo eseguiamo (inserendo contemporaneamente i nodi 1 e 2 in S).
Quindi, alla fine della prima iterazione si ha S = {1, 2}, T = {(1, 2)}. A questo punto viene
incrementato k ed essendo |T | < |N| − 1 l’algoritmo non termina.

49
ALGORITMO DI KRUSKAL
––––––––––––––––––––––––
1. Ordina gli archi di A per costi crescenti
2. Memorizzali nel vettore ORDINE[]
3. S := ∅, T := ∅, k := 1
. 4. Repeat
5. uv := ORDINE[k]
6. if H I (S ∪ {u, v}, T ∪ {uv}) aciclico
7. then { S := S ∪ {u, v}
8. T := T ∪ {uv}
}
9. k := k + 1
10. until (|T | = |N| − 1)
––––––––––––––––––––––––

Figura 5.9:

1
2 1 1,2 1
7 5 3,4 1
1,4 3
3 8 4
3 5,6
1
1,3 5
G(N,A) 2,3 7
4 8 8
2,4
4 6 ORDINE 4,5 8 COSTI
5

Figura 5.10: Prima iterazione algoritmo di Kruskal

50
All’iterazione seguente viene controllato l’arco (3, 4) = ORDINE[2]. Poiché l’inserimento in
T dell’arco (3, 4) non crea cicli in H (S, T ) lo eseguiamo (inserendo contemporaneamente i nodi
3 e 4 in S). Quindi, alla fine della seconda iterazione si ha S = {1, 2, 3, 4}, T = {(1, 2), (3, 4)}.
1
2 1 1,2 1
7 5 3,4 1
1,4 3
3 8 4
3 5,6
1
1,3 5
G(N,A) 2,3 7
4 8 8
2,4
4 6 ORDINE 4,5 8 COSTI
5

Figura 5.11: Seconda iterazione algoritmo di Kruskal

All’inizio della 5 iterazione dell’algoritmo (k = 5), la situazione è quella illustrata in Figura


5.12, con S = {1, 2, 3, 4, 5, 6} e T = {(1, 2), (1, 4), (3, 4), (4, 5), (5, 6)}. A questo punto ORDIN E[5] =
(1, 3). Tuttavia, l’inserimento di (1, 3) in T produrrebbe in H(S, T ) il ciclo C = {(1, 3), (3, 2), (2, 1)}.
1
2 1 1,2 1
7 5 3,4 1
1,4 3
3 8 4
3 5,6
1
1,3 5
G(N,A) 2,3 7
4 8 8
2,4
4 6 ORDINE 4,5 8 COSTI
5

Figura 5.12: Quinta iterazione algoritmo di Kruskal

Quindi (1, 3) non può essere inserito e l’iterazione termina. All’iterazione seguente viene se-
lezionato l’arco (2, 3) che per lo stesso motivo non può essere inserito. Analogamente all’iterazione
ancora successiva. Solo all’ultima iterazione viene inserito l’ultimo arco (4, 5). L’albero è quello
mostrato in Figura 5.13.
L’algoritmo di Krukal riassunto in Figura 5.9 è inizializzato con un foresta vuota S = ∅, T =
∅. Si osservi tuttavia che, per il Teorema di accrescimento, l’algoritmo poteva essere inizializzato
con una qualunque foresta parzialmente ottima.

5.4 L’algoritmo di Prim


In questo paragrafo viene presentato un nuovo teorema di accrescimento che permetterà la
definizione di un algoritmo di complessità migliore dell’algoritmo di Kruskal.

51
1
2 1
7 5

3 8
3
1
G(N,A)
4 8
4 6
5

Figura 5.13: L’albero ricoprente di costo minimo prodotto dall’algoritmo di Kruskal

Teorema 5.4.1 (Teorema di accrescimento) Se H(S, T ) (con S ⊂ N) è una foresta parzial-


mente ottima di un grafo non orientato connesso G(N, A) e uv è un arco di costo minimo di
δG (S). Allora H (S ∪ {v}, T ∪ {uv}) è una foresta parzialmente ottima.
Dim. Applichiamo l’algoritmo di Kruskal a partire da H(S, T ). Sia T ∗ l’albero minimo
costruito dall’algoritmo. Per costruzione T ⊂ T ∗ . Si osservi ora che T ∗ deve contenere almeno un
arco di δ(S). Altrimenti i nodi S e i nodi in N − S non sarebbero connessi in T ∗ , contraddizione
essendo T ∗ è un albero ricoprente. Sia uv il primo arco di δ(S) esaminato dall’algoritmo di
Kruskal. uv è un arco a costo minimo in δ(S) (perché l’algoritmo visita gli archi in ordine
di costi non decrescenti). Inoltre T ∪ {uv} non contiene cicli per il teorema 4.1.3. Quindi
T ∪ {uv} ⊆ T ∗ e quindi H (S ∪ {v}, T ∪ {uv} è parzialmente ottima.
Il Teorema 5.4.1 è alla base di una semplice strategia proposta da Prim nel 1957 per costruire
un albero ricoprente di costo minimo di un grafo G(N, A), costruendo una sequenza di alberi
parzialmente ottimi H(S, T ). Cominciando con l’albero composto di un solo nodo u e nessun
arco (S = {u} T = {∅}) (che è contenuto in ogni albero e quindi anche in ogni albero di costo
minimo), a ogni passo viene aggiunto a T un arco di δ(S) di costo minimo; contemporaneamente
viene aggiunto a S il secondo estremo dell’arco (il primo appartiene per definizione a S stesso).
Quando S contiene esattamente |N | nodi, allora l’albero sarà un albero ricoprente di G di costo
minimo.
10
1 s v4

5 12
v1
3 v3
v2 1
4 v5

Figura 5.14: Esempio di applicazione dell’algoritmo di Prim

Precisamente, l’algoritmo riceve in ingresso un grafo non orientato e connesso G(N, A) e un


vettore di costi c ∈ IR|A| e fornisce in uscita un albero ricoprente di costo minimo H(N, T ).
L’algoritmo inizializza l’insieme S dei nodi di H scegliendo un nodo qualsiasi s ∈ N.

52
ALGORITMO DI PRIM
––––––––––––––––––––––––
1. S := {s}, T = {∅}
. 2. Repeat
3. Scegli wv ∈ δ(S) di costo minimo
4. Poni { S := S ∪ {u, v}
5. T := T ∪ {uv}
6. until (|S| = |N|)
––––––––––––––––––––––––

Figura 5.15:

Complessità dell’algoritmo di Prim. Per valutare la complessità dell’algoritmo di Prim,


si osservi che a ogni esecuzione del blocco Repeat ... until viene inserito un nodo in S. Quindi
il blocco viene eseguito esattamente |N | − 1 volte. L’istruzione critica del blocco è la numero 3
in cui viene scelto un arco nel taglio δ(S): questa operazione, come descritto nel Lemma 4.3.1
ha costo O(m). Quindi, complesssivamente l’algoritmo di Prim ha costo O(nm). Si osservi che
l’algoritmo di Prim ha una complessità migliore dell’algoritmo di kruskal.

Una possibile implementazione dell’algoritmo di Prim utilizza la rappresentazione del grafo


a liste di adiacenza, quest’ultime realizzate con i vettori ST ART , EN D e NODI visti nel
Capitolo 4. Dovendo rappresentare anche il vettore dei costi, abbiamo appunto bisogno di un
ulteriore vettore costi con un numero di componenti pari alle componenti del vettore NODI, ove
l’i-esima componente è il costo dell’arco associato all’i-esima componente del vettore N ODI.
Fatte queste posizioni, l’algoritmo di Prim può essere sintetizzato come in Figura 5.16.
L’algoritmo riceve in ingresso un grafo non orientato e connesso G(N, A) e un vettore di costi
c ∈ IR|A| e fornisce in uscita un albero ricoprente di costo minimo H(N, T ). L’implementazione
utilizza la variabile CMIN per memorizzare il costo dell’arco di costo minimo nel taglio corrente.
Il secondo estremo di tale arco e l’arco stesso sono memorizzati, rispettivamente, nelle variabili
NODO e ARCO.

5.5 Esercizi di autovalutazione


Esercizio 5.5.1 Utilizzare gli algoritmi di visita per verificare se un grafo connesso G(N, A) è
aciclico.

Soluz. Si scelga un nodo qualsiasi e si costruisca l’albero di ricerca corrispondente, sia


H = (N, T ). Se T = A (ovvero H = G) allora il grafo è un albero e non contiene cicli. Se
T ⊂ A, allora preso un qualunque arco e ∈ A − T , e insieme agli archi in T formerà un ciclo
fondamentale di G e quindi G è ciclico.
Esercizio 5.5.2 Spiegare perché in un grafo sparso le complessità degli algoritmi di Kruskal e
di Prim sono equivalenti.

53
ALGORITMO DI PRIM (seconda versione)
––––––––––––––––––––––––––––––––––
1. S := {s}, T = {∅}
. 2. Repeat
3. CM IN = ∞
4. for w ∈ S
5. do {for p := ST ART [w] to END[w]
6. do if (COST I[p] < CM IN ) and (N ODI[p] ∈
/ S)
7. then {CM IN := COST I[p];
8. N ODO := N ODI[p];
9. ARCO := (w, N ODO);
10. };
11. }
12. S = S ∪ NODO;
13. T = T ∪ ARCO;
14. until (|S| = |N|)
––––––––––––––––––––––––––––––––––

Figura 5.16:

Soluz. Essendo m = O(n) si ha che O(nm) = O(n2 ) e O(m2 ) = O(n2 ).

Esercizio 5.5.3 Vero o Falso?: Se S è un sottoinsieme di N e H(S, T ) è un albero ricoprente


di G[S] allora per ogni Y ⊆ S il sottografo H[Y ] un albero ricoprente di G[Y ].

Soluz. Falso. Si consideri il contro esempio di Figura 5.17 dove si vede che il sottografo indotto
dai nodi {2, 3, 4, 5} non è un albero.

1
2 2
5 5
4 3 4 3

Figura 5.17: Controesempio all’affermazione dell’Esercizio 5.5.3

Esercizio 5.5.4 Vero o Falso?: Se S è un sottoinsieme di N e H(S, T ) è un albero ricoprente


di costo minimo di G[S] allora per ogni Y ⊆ S il sottografo H[Y ] un albero ricoprente di G[Y ].

Soluz. Falso. Il costo di H non svolge alcun ruolo. Per il resto la soluzione prosegue come
nell’Esercizio 5.5.3.

54
Esercizio 5.5.5 Vero o Falso?: Se H(N, T ) è un albero ricoprente di minimo costo di G allora
per ogni (u, v) ∈
/ T ogni arco del cammino che unisce u a v in H ha un costo inferiore a quello
di (u, v).

Soluz. Falso. L’enunciato va cambiato in ”... ha un costo non superiore a quello di (u, v).”
Quest’ultima affermazione segue direttamente dal Teorema dello scambio 5.1.2. Infatti, sia (x, y)
un arco sul cammino tale che cxy > cuv , allora l’albero ricoprente ottenuto da H sostituendo
(x, y) con (u, v) ha costo inferiore ad H.

Tuttavia possono esistere sul cammino archi di costo pari a (u, v).

Esercizio 5.5.6 Vero o Falso?: Làrco di costo minimo appartiene sempre allàlbero ricoprente
di costo minimo.

Soluz. Falso. L’enunciato va cambiato in ”Dato un arco di costo minimo esiste un albero
ricoprente di costo minimo che lo contiene”. Quest’ultima affermazione segue direttamente
dall’applicazione dell’algoritmo di Kruskal (un arco di costo minimo può essere scelto per primo).
L’enunciato è inoltre vero se esiste un solo arco di costo minimo.

Esercizio 5.5.7 Dato p (posizione nel vettore NODI) dedurre, utilizzando la struttura dati
ST ART /END, i nomi dei nodi estremi del corrispondente arco.

Esercizio 5.5.8 Scrivere l’Algoritmo di Kruskal completo (inserendo l’Algoritmo di Ricerca)


con la struttura dati ST ART /END.

55