TDT4120 Algoritmer og datastrukturer Interaktiv pensumguide · H2026
Fremgang
0/14
11
Grafer

Korteste vei fra alle til alle

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 VV 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 dijd_{ij} mellom hvert par av noder ii og jj. Resultatet er naturlig en V×VV \times V-matrise.

For å kunne rekonstruere selve veiene holder vi en forgjengermatrise Π\Pi: πij\pi_{ij} er forgjengeren til jj på en korteste vei fra ii. 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)L^{(m)}_{ij} være korteste vei fra ii til jj som bruker høyst mm kanter. Da er

Lij(m)=mink{Lik(m1)+wkj},L^{(m)}_{ij} = \min_{k}\left\{\, L^{(m-1)}_{ik} + w_{kj} \,\right\},

som er nøyaktig matrise-«multiplikasjon» der ++ erstatter ×\times og min\min erstatter \sum. Siden en enkel vei har høyst V1V-1 kanter, gir gjentatt «multiplikasjon» svaret. Slow-APSP gjør dette V1V-1 ganger i Θ(V4)\Theta(V^4); Faster-APSP bruker gjentatt kvadrering (L(2m)L^{(2m)} fra L(m)L^{(m)}) og kommer ned i Θ(V3lgV)\Theta(V^3 \lg V).

Floyd-Warshall bytter ut DP-dimensjonen: i stedet for antall kanter teller vi hvilke mellomliggende noder veien får lov til å bruke.

Hvert lag kk vurderer altså om det å slippe inn node kk som mellomstasjon korter ned noen vei. Tre nøstede løkker over kk, ii og jj gir kjøretiden:

Θ(V3)\Theta(V^3)

Θ(V3)\Theta(V^3), uavhengig av hvor mange kanter grafen har. Det slår VV 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 kk åpnes, er markert.

d(k)d^{(k)}121\to 2131\to 3141\to 4424\to 2
k=0k=0 (kun kanter)\infty33\infty\infty
k=1k=1 (via 11)\infty33\infty\infty
k=2k=2 (via 22)\infty33\infty\infty
k=3k=3 (via 33)7\mathbf{7}338\mathbf{8}\infty
k=4k=4 (via 44)7733882\mathbf{2}
Hvert lag åpner én ny mellomliggende node. Når node 33 slippes inn finnes en vei 1to3to21\\to 3\\to 2; node 44 åpner 4to24\\to 2. Fete celler ble forbedret i akkurat det laget.

I praksis trenger vi bare én matrise: oppdateringen dijmin(dij,dik+dkj)d_{ij} \leftarrow \min(d_{ij},\, d_{ik} + d_{kj}) kan gjøres på stedet, fordi rad kk og kolonne kk ikke endrer seg i runde kk.

  1. Initialiser

    Sett dij=wijd_{ij} = w_{ij} for kanter, 00 på diagonalen og \infty ellers. La πij=i\pi_{ij} = i der det finnes en kant, ellers udefinert.

  2. For hver mellomliggende node k=1Vk = 1 \dots V

    Vurder for alle par (i,j)(i, j) om veien ikji \to k \to j er kortere enn den nåværende dijd_{ij}.

  3. Oppdater hvis det lønner seg

    Hvis dik+dkj<dijd_{ik} + d_{kj} < d_{ij}: sett dij=dik+dkjd_{ij} = d_{ik} + d_{kj} og πij=πkj\pi_{ij} = \pi_{kj} (forgjengeren arves fra den andre halvdelen av veien).

  4. Les av

    Etter k=Vk = V inneholder dd alle korteste avstander, og π\pi lar deg rekonstruere hver vei.

Transitiv tillukning

Trenger vi bare å vite om det finnes en vei fra ii til jj — 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(k1)    (tik(k1)tkj(k1)).t^{(k)}_{ij} = t^{(k-1)}_{ij} \;\lor\; \left( t^{(k-1)}_{ik} \land t^{(k-1)}_{kj} \right).

Med min/+\min/+ byttet ut med /\lor/\land slipper vi å regne på vekter, og bitoperasjoner gjør det svært raskt i praksis.

Sjekk deg selv

Hva er DP-ideen i Floyd-Warshall?