Minimum spanning tree
In questa pagina 5
Nota sulle fonti: l'argomento è nel programma ufficiale ma non nel materiale del corso consultato; la nota è scritta dal programma con conoscenze standard.
Definizione (minimum spanning tree). Dato un grafo non diretto, pesato e connesso, un minimum spanning tree (MST, albero di copertura di peso minimo) è uno spanning tree (vedi Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →) che minimizza il peso totale . Ha archi. Applicazione tipica: collegare tutti i nodi di una rete (cavi, broadcast efficiente) con il costo minimo. Perché archi: uno spanning tree è un albero libero con vertici, quindi ha archi (proprietà P3 di Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →); con meno archi non sarebbe connesso, con di più avrebbe un ciclo, che si potrebbe spezzare togliendo un arco e quindi abbassando il peso (se i pesi sono positivi). Un albero di copertura compare anche nell'analisi dei circuiti (Grafo di una rete ed equazioni indipendentiIl grafo di una rete tiene solo i collegamenti (nodi e lati). Scelto un albero (n−1 lati, connesso, senza maglie), ogni lato di coalbero dà una LKT indipendente e ogni lato d'albero una LKC indipendente: in totale ℓ − n + 1 LKT e n − 1 LKC, che con le ℓ equazioni dei bipoli fanno 2ℓ equazioni per 2ℓ incognite.Grafo di una rete ed equazioni indipendenti →), dove però non si cerca di minimizzare un peso ma di scegliere le equazioni indipendenti. Se i pesi sono tutti distinti l'MST è unico; altrimenti ce ne può essere più d'uno.
Esempio. Nel grafo , , , , , , , , l'MST ha archi e peso .
Proprietà del taglio
Un taglio è una partizione (Operazioni tra insiemiUnione, intersezione, differenza, complementare e prodotto cartesiano, con le leggi di De Morgan e il legame con la logica.Operazioni tra insiemi →: unione disgiunta, ) con non vuoti; un arco lo attraversa se ha un estremo in e uno in .
Proprietà (del taglio). Sia un sottoinsieme degli archi di un qualche MST e sia un taglio che nessun arco di attraversa. Se è un arco di peso minimo tra quelli che attraversano il taglio, allora è ancora contenuto in un MST.
Esempio. Con il taglio contro il resto gli archi che lo attraversano pesano : l'arco appartiene a un MST. Un secondo taglio, con già scelto e , : gli archi che attraversano sono , , , ; il minimo è , quindi è ancora contenuto in un MST.
Dimostrazione (per scambio; usa le tecniche di Tecniche di dimostrazioneDimostrazione diretta, per contronominale e per assurdo di un teorema del tipo ipotesi implica tesi.Tecniche di dimostrazione →). Sia un MST che contiene . Se non c'è nulla da dimostrare. Altrimenti contiene un ciclo (aggiungere un arco a un albero ne crea esattamente uno: gli estremi di erano già collegati da un cammino in ); questo attraversa il taglio in un altro arco (un ciclo attraversa un taglio un numero pari di volte, quindi almeno due), con per la minimalità di . è uno spanning tree (connesso, archi) con , quindi è un MST; contiene perché (nessun arco di attraversa il taglio) e contiene .
Entrambi gli algoritmi sono greedy: a ogni passo aggiungono un arco "sicuro" in base alla proprietà del taglio.
Algoritmo di Kruskal
Definizione (algoritmo di Kruskal). Si scorrono gli archi per peso crescente e si aggiunge l'arco se non crea un ciclo con quelli già scelti, cioè se collega due componenti diverse.
Esempio. Nello stesso grafo Kruskal sceglie , , , , : peso .
Algoritmo Kruskal(G)
ordina E per peso crescente
ogni vertice forma una componente da solo
T <- insieme vuoto
for each e = (u, v) in E nell'ordine do
if componente(u) != componente(v) then
T <- T + {e}; fondi le due componenti
return TIl taglio della proprietà è, a ogni passo, una componente contro il resto: l'arco minimo tra componenti diverse è sicuro. Con una struttura union-find (partizioni con find e union) l'ordinamento costa (perché ) e le operazioni sulle componenti sono quasi costanti: totale . Passaggi: l'ordinamento ottimo per confronti costa (Algoritmi di ordinamentoSelection sort e insertion sort (quadratici, con invarianti), merge sort (divide et impera, Theta(n log n)), quick sort (caso pessimo quadratico, medio n log n), heap sort (in loco, Theta(n log n)), ordinamento senza confronti per chiavi intere in un intervallo piccolo, limite inferiore Omega(n log n) per gli algoritmi basati su confronti.Algoritmi di ordinamento →); da segue (Esponenziale e logaritmoLa funzione esponenziale a^x (base positiva diversa da 1) e la sua inversa, il logaritmo in base a, con grafici e proprietà.Esponenziale e logaritmo →: ), quindi . Le componenti sono una partizione di (Operazioni tra insiemiUnione, intersezione, differenza, complementare e prodotto cartesiano, con le leggi di De Morgan e il legame con la logica.Operazioni tra insiemi →): find(u) dà il rappresentante della componente di , union fonde due componenti; i due estremi sono nella stessa componente se e solo se find(u) = find(v). L'idea è la stessa dell'etichetta v.ID = k della componente connessa in Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →, ma aggiornabile in modo efficiente. È un algoritmo greedy: a ogni passo prende la scelta localmente migliore (l'arco più leggero utile), e la proprietà del taglio garantisce che non ci si pente.
Algoritmo di Prim
Definizione (algoritmo di Prim). Si parte da un vertice e si fa crescere un unico albero, aggiungendo sempre l'arco di peso minimo che collega l'albero a un vertice esterno. Si realizza come Dijkstra (vedi Cammini minimi e algoritmo di DijkstraGrafi pesati, lunghezza di un cammino e distanza; sottocammini di un cammino minimo; problema SSSP; algoritmo di Dijkstra con cloud e priority queue, rilassamento degli archi, esempio svolto; correttezza (due lemmi) e complessità O(min(n^2, (n+m) log n)) con lista non ordinata o heap; pesi non negativi.Cammini minimi e algoritmo di Dijkstra →) con una coda con priorità in cui la chiave di un vertice esterno è il peso del miglior arco che lo collega all'albero.
Esempio. Da Prim aggiunge , , , , : peso , come con Kruskal. L'albero cresce da un solo vertice, mentre Kruskal fa crescere una foresta di più alberi che si fondono; l'invariante di Prim è che l'insieme degli archi scelti forma un albero che contiene , e il taglio della proprietà è "albero contro resto".
Algoritmo Prim(G, s)
forall v in V do v.D <- +infinito; v.parent <- null
s.D <- 0
Q <- coda con priorità con tutti i vertici (chiave v.D)
while Q non è vuota do
u <- Q.removeMin() (u entra nell'albero, insieme all'arco (u.parent, u))
forall (u, v) in E con v in Q do
if w(u, v) < v.D then v.D <- w(u, v); v.parent <- u; aggiorna QLa differenza con Dijkstra è nella regola di rilassamento: qui v.D <- w(u,v), non u.D + w(u,v). Gli archi (v.parent, v) formano l'MST. Complessità: come Dijkstra, con heap e con lista non ordinata.
Esempio svolto
Kruskal (ordinando i pesi uguali in ordine alfabetico). Archi ordinati: , , , , , , , , . Si aggiungono (componenti ), (), (), (fonde in ), (fonde tutto: , 5 archi: fine); , , , sarebbero scartati perché creano cicli. Peso totale .
| arco (peso) | componenti degli estremi | azione | componenti dopo |
|---|---|---|---|
| , | aggiunto | ||
| , | aggiunto | , | |
| , | aggiunto | , , | |
| , | aggiunto | , | |
| , | aggiunto | (5 archi, fine) |
Gli archi successivi , , , avrebbero entrambi gli estremi nella stessa componente: aggiungerli chiuderebbe un ciclo.
Prim da . Si aggiungono nell'ordine , , , , : stesso albero, peso . (Verificato con un'implementazione in Python di entrambi.) Chiavi D dei vertici fuori dall'albero, in ordine :
| passo | entra nell'albero | chiavi dopo l'aggiornamento | nota |
|---|---|---|---|
| inizio | archi di | ||
| 1 | (via , 7) | non migliora; | |
| 2 | (via , 9) | ; | |
| 3 | (via , 2) | ||
| 4 | (via , 9) | ||
| 5 | (via , 6) | fine |
Il peso è la somma delle chiavi al momento dell'ingresso: . Si noti che le chiavi non sommano le distanze dalla sorgente (come in Dijkstra) ma tengono il solo peso dell'arco che collega il vertice all'albero.
Attenzione: l'MST non coincide con l'albero dei cammini minimi di Dijkstra da (lì ha parent con l'arco , qui l'arco è ): minimizzano grandezze diverse (peso totale contro distanze dalla sorgente).
Errori comuni
- Confondere MST e albero dei cammini minimi: obiettivi diversi.
- In Prim usare
u.D + w(u,v)(è Dijkstra). - In Kruskal controllare solo che l'arco non sia già in invece di verificare che non crei un ciclo.
- Applicare MST a grafi non connessi senza ottenere una foresta di copertura (un MST per componente).
Versione ripasso
- MST: spanning tree di peso totale minimo in un grafo non diretto, pesato e connesso; ha archi (vedi Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →). Se i pesi sono distinti è unico.
- Taglio: partizione con non vuoti; un arco lo attraversa se ha un estremo in e l'altro in .
- Proprietà del taglio: se è contenuto in un MST, è un taglio che nessun arco di attraversa e è un arco di peso minimo che lo attraversa, allora è contenuto in un MST. Prova per scambio: nel ciclo che forma c'è un altro arco che attraversa il taglio, con ; non pesa di più.
- Kruskal: si scorrono gli archi per peso crescente e si aggiunge se
componente(u) != componente(v), fondendo le componenti con union-find. Costo , perché da . L'arco minimo tra due componenti è sicuro per la proprietà del taglio. - Prim: si cresce un unico albero da aggiungendo ogni volta l'arco minimo verso un vertice esterno. È come Dijkstra (vedi Cammini minimi e algoritmo di DijkstraGrafi pesati, lunghezza di un cammino e distanza; sottocammini di un cammino minimo; problema SSSP; algoritmo di Dijkstra con cloud e priority queue, rilassamento degli archi, esempio svolto; correttezza (due lemmi) e complessità O(min(n^2, (n+m) log n)) con lista non ordinata o heap; pesi non negativi.Cammini minimi e algoritmo di Dijkstra →) ma con la regola
v.D <- w(u,v), nonu.D + w(u,v). Costo con heap, con lista. - Esempio (grafo di Dijkstra: , , , , , , , , ):
- Kruskal: , , , , ; gli archi , , , chiuderebbero cicli e sono scartati. Peso .
- Prim da : , , , , . Stesso peso .
- Non coincide con l'albero dei cammini minimi di Dijkstra da : lì ha parent con , qui l'arco è . Le due cose minimizzano grandezze diverse.
- Peso di Prim: è la somma delle chiavi al momento dell'ingresso. Ogni chiave è il peso dell'arco che collega il vertice all'albero, non una distanza dalla sorgente.
- Kruskal e il taglio: a ogni passo una componente è un lato del taglio contro il resto, e l'arco minimo che la lascia è sicuro.
- Errori tipici: confondere MST e cammini minimi; usare
u.D + w(u,v)in Prim; in Kruskal controllare solo che l'arco non sia già in invece di verificare che non crei un ciclo; applicare l'MST a un grafo non connesso senza ottenere una foresta.