Cammini minimi e algoritmo di Dijkstra
In questa pagina 5
Definizione (lunghezza di un cammino e distanza). Sia un grafo non diretto e pesato, con (esempio: rete di aeroporti con distanze in km; sono i numeri reali: Insiemi numerici (N, Z, Q, R)Naturali, interi, razionali e reali, perché la radice di 2 obbliga a passare da Q a R, e gli intervalli.Insiemi numerici (N, Z, Q, R) →). Il problema è lo stesso dell'instradamento dei pacchetti in Internet, dove i pesi sono costi dei collegamenti e l'algoritmo è detto link state (Algoritmi di instradamento - link state e distance vectorL'instradamento (routing) trova il percorso di costo minimo in un grafo pesato in cui i router sono nodi e le reti tra due router sono archi. Link state: ogni router diffonde con un flooding i pacchetti LSP sui propri collegamenti, ricostruisce tutto il grafo e applica Dijkstra (nodi con stato (distanza, permanente o temporaneo)). Distance vector: ogni router conosce solo i vicini e scambia con loro il proprio vettore delle distanze, aggiornato con Bellman-Ford $D_{A,w}=\min{D_{A,w},,D_{A,Y}+d_{Y,w}}$; converge in al più $n-1$ giri ma può soffrire del conteggio all'infinito (limite a 16, hold down, aggiornamenti immediati, split horizon con poison reverse). Path vector: ogni annuncio porta l'intero cammino, si sceglie per politica e non per costo, e i cicli si scoprono trovando sé stessi nel cammino. Dijkstra è corretto se i costi sono non negativi (invariante: un nodo permanente ha già la distanza vera); Bellman-Ford è corretto perché dopo $k$ giri conosce i cammini minimi con al più $k+1$ archi.Algoritmi di instradamento - link state e distance vector →, Instradamento e inoltroL'inoltro (forwarding) mette il pacchetto sulla strada verso la destinazione, un salto alla volta (hop by hop). Se la destinazione è nella stessa rete del mittente l'inoltro è diretto (si usa l'ARP per il MAC del destinatario), altrimenti è indiretto: il pacchetto va al router successivo (next hop) indicato dalla tabella di instradamento, o al default gateway. Con le netmask: l'inoltro è diretto attraverso l'interfaccia $x$ se $\text{IP(dst)}\ \text{AND}\ \text{NM}(x)=\text{IP}(x)\ \text{AND}\ \text{NM}(x)$; altrimenti si scorre la tabella dalla maschera più lunga (longest prefix match) e si usa il primo match. La riga con rete $0.0.0.0$ e maschera $0.0.0.0$ (default route) corrisponde sempre. L'aggregazione di rotte (route aggregation) riduce la tabella, e nell'inoltro con etichette (MPLS) la tabella si consulta per indice.Instradamento e inoltro →). La lunghezza di un cammino è ; se per ogni arco è il numero di archi. La distanza è la minima lunghezza di un cammino da a e il cammino che la realizza è un cammino minimo.
Esempio. Nel grafo dell'esempio svolto il cammino ha lunghezza e .
Proposizione (sottostruttura ottima dei cammini minimi). Se è un cammino minimo da a , allora è un cammino minimo da a per ogni . Dimostrazione (per assurdo: Tecniche di dimostrazioneDimostrazione diretta, per contronominale e per assurdo di un teorema del tipo ipotesi implica tesi.Tecniche di dimostrazione →): se esistesse un cammino più corto da a , sostituendolo nel cammino originale si otterrebbe un cammino da a più corto, contro l'ipotesi. In formule: la lunghezza del cammino originale è ; sostituendo il termine centrale con uno strettamente minore si ottiene una somma strettamente minore.
Esempio. Se è un cammino minimo da a , allora è un cammino minimo da a ().
SSSP (single-source shortest paths): dato , determinare le distanze da a tutti gli altri vertici e i relativi cammini minimi (rappresentati dai campi parent). Si assume che non abbia cicli di peso negativo (altrimenti la lunghezza potrebbe diventare arbitrariamente piccola). Se tutti i pesi sono basta la BFS in (vedi 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 →).
Algoritmo di Dijkstra
Definizione (algoritmo di Dijkstra). Sviluppato nel 1956, richiede pesi non negativi. Generalizza la BFS: a partire da fa crescere una cloud di vertici di cui si conoscono già distanza da e cammino minimo.
Esempio. Sul grafo dell'esempio svolto Dijkstra da estrae i vertici in ordine di distanza: , , , , , .
La stessa idea della BFS (esplorare per "cerchi" crescenti attorno a ) vale qui con una differenza: nella BFS i cerchi sono i livelli (distanza in archi, 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 →); con i pesi bisogna estrarre ogni volta il vertice di distanza corrente minima, e per questo serve una coda con priorità al posto della coda FIFO.
- La cloud parte con .
- Ogni vertice ha
v.D= distanza corrente da (lunghezza del miglior cammino noto con vertici interni nella cloud) ev.parent= predecessore su quel cammino. - A ogni iterazione entra nella cloud il vertice esterno con
Dminimo; poi si rilassano gli archi con ancora fuori: seu.D + w(u,v) < v.Dsi ponev.D <- u.D + w(u,v)ev.parent <- u. - I vertici fuori dalla cloud stanno in una coda con priorità con chiave
v.D(vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →).
Algoritmo ShortestPaths(G, s)
Input: grafo non diretto G=(V,E,w), pesi >= 0, sorgente s
Output: distanze e cammini minimi da s, nei campi v.D e v.parent
s.D <- 0; s.parent <- null
forall v in V - {s} do v.D <- +infinito; v.parent <- null
Q <- coda con priorità con tutti i vertici (chiave v.D)
while Q non è vuota do
u <- Q.removeMin()
forall (u, v) in E con v in Q do
if u.D + w(u,v) < v.D then
v.D <- u.D + w(u,v); v.parent <- u
aggiorna Q per la nuova chiave di vEsempio svolto
Vertici e archi (peso): , , , , , , , , . Sorgente .
| Iterazione | Entra in cloud | Distanze correnti dopo il rilassamento |
|---|---|---|
| inizio | () | |
| 1 | () | (: ; : ) |
| 2 | () | (: ; : ) |
| 3 | () | (: ) |
| 4 | () | invariato (: ) |
| 5 | () | — |
Distanze da : , , , , . Cammini (da parent): ; ; ; ; . Quindi il cammino minimo è di lunghezza . Si noti che il cammino con meno archi verso , cioè di lunghezza , non è il minimo: vale . Il cammino con il minor numero di archi (BFS) e quello di peso minimo sono in generale diversi. Controllo dei rilassamenti dell'iterazione 2: da (distanza ) l'arco dà , aggiorna e il suo parent da a ; l'arco dà , aggiorna da a ; l'arco non si rilassa perché è già in cloud. (Verificato eseguendo un'implementazione con heap; a parità e entrano in cloud in uno dei due ordini.)
Correttezza
Segue da due lemmi (la dimostrazione del Lemma 2 non è richiesta nel dettaglio). Alla fine di ogni iterazione del while, per ogni :
- Lemma 1. Se
v.Dallorav.parentnull; sev.D, il cammino ottenuto risalendo da conparentè un cammino da a di lunghezzav.D. - Lemma 2. Quando viene estratto da ,
u.D.
Idea per il Lemma 2. Se per assurdo u.D , si considera un cammino minimo da a e il primo vertice fuori dalla cloud su di esso. Il predecessore di è in cloud, quindi l'arco verso è stato rilassato e z.D u.D (qui servono i pesi : ). Allora sarebbe stato estratto prima di , contraddizione. Perché z.D : il cammino minimo da a ha come ultimo arco quello dal predecessore in cloud; per il Lemma 2 applicato a si ha y.D , e il rilassamento di ha posto z.D y.D (sottostruttura ottima). Perché : sta sul cammino minimo prima di e i pesi dopo di lui sono , quindi la distanza non può diminuire proseguendo. Con un peso negativo questa seconda disuguaglianza cade, ed è esattamente il punto che si rompe nell'esempio seguente.
Perché servono pesi non negativi. In un grafo diretto con archi di peso , di peso e di peso (nessun ciclo negativo) Dijkstra estrae con , ma il cammino vale . Passo per passo: si estrae () e si rilassano () e (); il minimo in è con , che entra in cloud e non viene più aggiornato; si estrae () e si controlla , ma non è più in e il rilassamento viene saltato: l'algoritmo termina con invece di . (In un grafo non diretto un solo arco negativo è già un ciclo negativo: si percorre avanti e indietro.)
Complessità
Dipende dall'implementazione della coda . Ogni vertice è estratto una volta ( removeMin), si fanno al più aggiornamenti di chiave (un rilassamento per arco: l'arco è rilassato quando viene estratto il primo dei suoi due estremi, perché all'estrazione del secondo il primo è già fuori da ), e ogni vertice mantiene un puntatore alla propria entry in (altrimenti trovare l'entry da aggiornare costerebbe una ricerca; è l'idea delle posizioni di Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →). Le operazioni sullo heap sono quelle di HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap → e la costruzione iniziale è lineare (Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →).
| Operazione su | Heap | Lista doppiamente concatenata non ordinata |
|---|---|---|
| costruzione iniziale | ||
removeMin |
||
| aggiornamento di una chiave |
Nello heap l'aggiornamento di una chiave diminuita richiede un up-heap bubbling in (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). Totale:
- heap: ;
- lista non ordinata: (perché ).
Teorema (complessità dell'algoritmo di Dijkstra). La complessità di
ShortestPaths(G, s)è : con la scelta migliore dell'implementazione. Per grafi sparsi () conviene lo heap (); per grafi densi () la lista ( contro ).
Esempio. Con e conviene lo heap: contro (, quindi ).
Il grafico fissa e fa variare : la curva dello heap cresce linearmente con , quella della lista è costante. Si incontrano a , cioè : sopra questo valore (grafi molto densi, in cui è una frazione consistente del massimo ) conviene la lista non ordinata; sotto (grafi sparsi) lo heap. Le costanti sono ignorate, quindi il punto di incrocio è indicativo (Notazione asintoticaDefinizioni di O, Omega, Theta e o piccolo con le costanti c ed n0; esempi con costanti esplicite; proprietà (polinomi, esponenziali, logaritmi, somme, implicazioni tra notazioni); sommatorie notevoli; terminologia (logaritmica, lineare, polinomiale, esponenziale).Notazione asintotica →).
Grafico interattivo: Costo di Dijkstra con n = 1000 in funzione di m: lista non ordinata n² = 10⁶ (costante), heap (n+m)·log₂n; si incrociano a m ≈ 99000, sotto il quale lo heap vince
Errori comuni
- Usare Dijkstra con pesi negativi.
- Dimenticare l'aggiornamento di
parentquando si miglioraD(si ottengono le distanze ma non i cammini). - Rilassare anche archi verso vertici già nella cloud (inutile, non cambia la distanza finale).
- Dire che costa sempre : dipende dall'implementazione di ; non tiene conto degli aggiornamenti di chiave su un grafo denso.
Versione ripasso
- Lunghezza e distanza: in un grafo non diretto pesato , con , la lunghezza di un cammino è la somma dei pesi; è la minima lunghezza. Con è il numero di archi (vedi Insiemi numerici (N, Z, Q, R)Naturali, interi, razionali e reali, perché la radice di 2 obbliga a passare da Q a R, e gli intervalli.Insiemi numerici (N, Z, Q, R) →).
- Sottostruttura ottima: se è minimo, ogni sottocammino è minimo. Per assurdo: un sottocammino più corto darebbe un cammino più corto da a (Tecniche di dimostrazioneDimostrazione diretta, per contronominale e per assurdo di un teorema del tipo ipotesi implica tesi.Tecniche di dimostrazione →).
- SSSP: distanze e cammini da (campi
Deparent), senza cicli di peso negativo. Con tutti i pesi uguali a basta la BFS, (vedi 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 →). - Dijkstra (pesi ): cloud iniziale ; = coda con priorità dei vertici fuori dalla cloud, con chiave
v.D(vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →). A ogni passo si estrae conDminimo e si rilassano gli archi con : seu.D + w(u,v) < v.D, si aggiornanov.D,v.parente la chiave in . - Pseudocodice:
s.D <- 0, gli altriD <- +inf,parent <- null; finché non è vuota:u <- Q.removeMin(), poi rilassamento degli archi con . - Esempio (archi , , , , , , , , ; sorgente ): estrazioni , , , , , . Cammino minimo : , lunghezza . Il cammino vale e vale : il cammino con meno archi verso non è quello di peso minimo.
- Correttezza: Lemma 1: risalendo da
parentsi ottiene un cammino di lunghezzaD. Lemma 2: all'estrazioneu.D, per assurdo, con il primo vertice fuori dalla cloud su un cammino minimo; qui servono pesi . - Pesi negativi: con di peso , di peso , di peso , Dijkstra dà , ma il cammino vale .
- Costo:
removeMine al più aggiornamenti. Heap: ; lista non ordinata: ; in totale , cioè heap per grafi sparsi e lista per densi (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →, Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →). Con e lo heap fa circa operazioni, contro . - Operazioni su : costruzione in entrambe;
removeMincon heap, con lista; aggiornamento di chiave con heap, con lista doppiamente concatenata che tiene il puntatore alla entry (vedi Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →). - Errori tipici: pesi negativi;
parentnon aggiornato (si hanno distanze ma non cammini); dire senza distinguere l'implementazione.
Teoria collegata
- Paradigma greedy - scelta golosa, sottostruttura e selezione delle attività
- Ricerca nello spazio degli stati - formulazione dei problemi e ricerca non informata
- Code con priorità
- Grafi - definizioni e proprietà
- Grafi diretti e ordinamento topologico
- Minimum spanning tree
- Visite di grafi - BFS e DFS
- Algoritmi di instradamento - link state e distance vector
- Protocolli di instradamento - RIP, OSPF e BGP
- RIP in Katharà