Kjernepensum~40 min lesing · Korteste vei · Dynamisk programmering
Til nå har vi funnet korteste vei fra én kilde. Nå skal vi løse alle par på
én gang. Det virker som V kjøringer av en énkilde-algoritme, men når alle par
skal løses blir de overlappende delinstansene synlige — og
dynamisk programmering blir det
naturlige verktøyet, spesielt på tette grafer.
Problemet og forgjengerstrukturen
Alle-til-alle korteste veier
(APSP) tar en vektet rettet graf og spør om den korteste avstanden dij
mellom hvert par av noder i og j. Resultatet er naturlig en
V×V-matrise.
For å kunne rekonstruere selve veiene holder vi en
forgjengermatriseΠ: πij er forgjengeren til j på en korteste
vei fra i. Der énkilde-algoritmer har ett forgjengertre, har APSP altså ett
tre per startnode, samlet i én matrise. Negative kanter er tillatt så lenge det
ikke finnes negative sykler.
Fra matrise-«multiplikasjon» til Floyd-Warshall
En første DP bygger korteste veier etter antall kanter de bruker. La
Lij(m) være korteste vei fra i til j som bruker høyst m kanter. Da
er
Lij(m)=kmin{Lik(m−1)+wkj},
som er nøyaktig matrise-«multiplikasjon» der + erstatter × og min
erstatter ∑. Siden en enkel vei har høyst V−1 kanter, gir gjentatt
«multiplikasjon» svaret. Slow-APSP gjør dette V−1 ganger i
Θ(V4); Faster-APSP bruker gjentatt kvadrering
(L(2m) fra L(m)) og kommer ned i Θ(V3lgV).
Floyd-Warshall bytter ut DP-dimensjonen: i stedet for antall kanter teller vi
hvilke mellomliggende noder veien får lov til å bruke.
Hvert lag k vurderer altså om det å slippe inn node k som mellomstasjon
korter ned noen vei. Tre nøstede løkker over k, i og j gir kjøretiden:
— Θ(V3), uavhengig av hvor mange kanter grafen har. Det slår
V Dijkstra-kjøringer på tette grafer og håndterer i tillegg negative kanter.
DP-lagene i arbeid
Tabellen under viser et lite eksempel: avstandsmatrisen for fire noder mens vi
slipper inn én og én mellomliggende node. Celler som forbedres når node k
åpnes, er markert.
d(k)
1→2
1→3
1→4
4→2
k=0 (kun kanter)
∞
3
∞
∞
k=1 (via 1)
∞
3
∞
∞
k=2 (via 2)
∞
3
∞
∞
k=3 (via 3)
7
3
8
∞
k=4 (via 4)
7
3
8
2
Hvert lag åpner én ny mellomliggende node. Når node 3 slippes inn finnes en vei 1to3to2; node 4 åpner 4to2. Fete celler ble forbedret i akkurat det laget.
I praksis trenger vi bare én matrise: oppdateringen
dij←min(dij,dik+dkj) kan gjøres på stedet, fordi
rad k og kolonne k ikke endrer seg i runde k.
Initialiser
Sett dij=wij for kanter, 0 på diagonalen og ∞ ellers. La
πij=i der det finnes en kant, ellers udefinert.
For hver mellomliggende node k=1…V
Vurder for alle par (i,j) om veien i→k→j er kortere enn den
nåværende dij.
Oppdater hvis det lønner seg
Hvis dik+dkj<dij: sett dij=dik+dkj og
πij=πkj (forgjengeren arves fra den andre halvdelen av veien).
Les av
Etter k=V inneholder d alle korteste avstander, og π lar deg
rekonstruere hver vei.
Transitiv tillukning
Trenger vi bare å vite om det finnes en vei fra i til j — ikke hvor lang —
kjører vi Transitive-Closure: samme tre løkker, men med boolske verdier i
stedet for avstander. Rekurrensen blir
tij(k)=tij(k−1)∨(tik(k−1)∧tkj(k−1)).
Med min/+ byttet ut med ∨/∧ slipper vi å regne på vekter, og
bitoperasjoner gjør det svært raskt i praksis.