CMD + K

Algoritmer og datastrukturerKorteste vei mellom alle parBegreper & formler10
7 min lesing1 video

Korteste vei mellom alle par

Noen ganger trenger vi korteste vei mellom alle par av noder samtidig. Vi kan se det som dynamisk programmering over matriser: enten ved gjentatt å forlenge stier, eller ved Floyd-Warshall som tillater stadig flere mellomnoder. Transitiv tillukning er en boolsk variant.

Læringsmål
  • 01Forklare alle-par-problemet og når det lønner seg framfor å kjøre Dijkstra fra hver node
  • 02Utlede Floyd-Warshall-rekurrensen ved å la stier bruke stadig flere mellomnoder, og forklare hvorfor k-løkka må ligge ytterst
  • 03Implementere Floyd-Warshall i Theta(V^3) og oppdage negative sykler ved å se på diagonalen i avstandsmatrisen
  • 04Forklare transitiv tillukning som den boolske varianten der min/pluss erstattes av eller/og

Hele kartet på én gang

Tenk på et veikart med byer og veistrekninger der hver strekning har en lengde. alle-par-problemet handler om å fylle ut en hel reiseavstandstabell: for hvert par av byer, hvor langt er den korteste ruta? Det er noe annet enn å spørre om korteste vei fra én startby til resten. Her vil vi ha svaret for alle startpunkter samtidig.

En naiv idé er å kjøre en enkeltkilde-algoritme én gang per startnode. Med Dijkstra fra hver av de nodene får vi — brukbart for tynne grafer med bare positive vekter. Men har grafen negative kanter, eller er den tett slik at nærmer seg , lønner det seg å angripe alle par direkte. Det er det dette kapittelet handler om, og verktøyet er dynamisk programmering over matriser.

Vi representerer grafen med en vektmatrise . Celle er vekten på kanten fra node til node . Finnes det ingen slik kant, setter vi , og avstanden fra en node til seg selv er . Resultatet vi er ute etter er en avstandsmatrise , der er den korteste avstanden fra til . Grafen under har fire noder, og til høyre står den ferdige avstandsmatrisen den gir.

382511234Kanten 1→3 koster 8 direkte, men ruta 1→2→3 koster bare 3+2 = 5. Floyd-Warshall finner den korteste.
FIGVektet rettet graf G
D[i][j] = korteste avstand fra i til j123412340356023010Uthevet (1,3) = 5 kom fra ruta 1→2→3, ikke kanten 1→3 = 8. ∞ betyr ingen vei finnes.
FIGAvstandsmatrise D for grafen G

Legg merke til at den direkte kanten fra node 1 til node 3 koster 8, men avstandsmatrisen sier 5. Den korteste ruta går via node 2: med kostnad . Hele kunsten ligger i å oppdage slike omveier som faktisk er kortere. Symbolet i matrisen markerer par der ingen rettet vei finnes i det hele tatt.

Å bygge stier av kortere stier

Den sentrale ideen er at en lang sti kan settes sammen av to kortere. Hvis den korteste veien fra til går gjennom en node underveis, så består den av to deler: korteste vei fra til , og korteste vei fra til . Knekker vi en optimal sti på et mellompunkt, må begge halvdelene selv være optimale — ellers kunne vi byttet ut en halvdel med noe kortere og fått en enda kortere helsti. Dette er optimal delstruktur, og det er nettopp den egenskapen som gjør dynamisk programmering mulig.

En første DP-algoritme bygger på lengde: la være den korteste avstanden fra til som bruker høyst kanter. For å utvide fra til kanter prøver vi alle mulige nest-siste noder:

for hver node i:
    for hver node j:
        L_ny[i][j] = L[i][j]
        for hver node k:
            if L[i][k] + W[k][j] < L_ny[i][j]:
                L_ny[i][j] = L[i][k] + W[k][j]

Dette ligner mistenkelig på matrisemultiplikasjon: i stedet for «gang og summer» gjør vi «adder og ta minimum». Siden en korteste vei uten negative sykler aldri bruker mer enn kanter, holder det å utvide ganger. Hver utvidelse er , så hele denne «Slow-APSP» blir — riktig, men treig.

Trikset for å få den ned er at «matriseproduktet» er assosiativt. I stedet for å gange inn én ekstra kant av gangen, kan vi bruke gjentatt kvadrering: kvadrer matrisen for å doble kantbudsjettet hver runde. Etter kvadreringer dekker vi alle stier. Det gir ƒgjentatt kvadrering av matriser, altså . Bedre, men det finnes noe enda renere.

Floyd-Warshall: slipp til én mellomnode av gangen

floyd-warshall bytter perspektiv. I stedet for å telle kanter, nummererer vi nodene og tillater stier å bruke stadig flere av dem som mellomnodeer underveis. La være korteste avstand fra til der bare nodene får brukes mellom endepunktene.

For hver stiller vi ett spørsmål om hver av de cellene: hjelper det å få lov til å bruke node ? Det er bare to muligheter. Enten holder vi oss til den beste ruta som klarte seg uten , eller så går vi via — først korteste vei fra til (som allerede smetter gjennom ), deretter fra til . Vi tar det minste av de to:

ƒfloyd-warshall-rekurrensen

Basistilfellet , der ingen mellomnoder er lov, er rett og slett vektmatrisen . Algoritmen blir tre nøstede løkker — den ytterste over , de to indre over alle par :

D = kopi av W            # ingen mellomnoder ennå
for k fra 0 til V-1:
    for i fra 0 til V-1:
        for j fra 0 til V-1:
            if D[i][k] + D[k][j] < D[i][j]:
                D[i][j] = D[i][k] + D[k][j]

Det fine her er at vi trygt kan oppdatere matrisen på plass. Når vi behandler runde , leser vi rad og kolonne — men de endrer seg ikke i denne runden, fordi (en vei til vinner ingenting på å få lov til å bruke som mellomnode). Derfor trenger vi ikke to separate matriser; én holder.

At rekkefølgen på ligger ytterst er helt avgjørende. Vi låser opp én mellomnode om gangen, og når node slippes til, er alle stier som bare bruker lavere-nummererte mellomnoder allerede ferdig regnet. Bytter du om løkkene, mister du den garantien og kan få feil svar. Kjøretiden er ƒfloyd-warshall, kjøretid, , med tre løkker over alle nodetripler. Den er like enkel som den er rask, og den slår per-node-Dijkstra på tette grafer.

Negative vekter og negative sykler

Floyd-Warshall takler negative kanter helt fint, så lenge grafen ikke har en negativ syklus. En negativ syklus er en rundtur der vektene summerer til noe under null. Da finnes det ingen veldefinert korteste avstand: du kan kjøre rundt en gang til og en gang til, og avstanden synker uten bunn.

Algoritmen gir oss en pen måte å oppdage dette på. Etter at den har kjørt ferdig, ser vi på diagonalen. Hvis noen har blitt negativ, betyr det at det finnes en vei fra tilbake til med samlet negativ vekt — altså en negativ syklus som ligger på. En ren graf uten slike sykler skal ha bare nuller på diagonalen.

Når vi bare spør om det finnes en vei

Noen ganger bryr vi oss ikke om hvor langt, bare om det går an å komme fra til i det hele tatt. Det kalles transitiv tillukning: den boolske matrisen der er sann hvis det finnes en rettet vei fra til , og usann ellers.

Vi kan løse det med nøyaktig samme maskineri, men bytte ut tallregningen med logikk. Der Floyd-Warshall tok minimum av en sum, tar vi nå eller av en og: en vei fra til gjennom finnes hvis den fantes uten , eller hvis det både finnes en vei fra til og en fra til .

ƒtransitiv tillukning

T = (W[i][j] != ∞)       # sann der det finnes en direkte kant
for k fra 0 til V-1:
    for i fra 0 til V-1:
        for j fra 0 til V-1:
            T[i][j] = T[i][j] or (T[i][k] and T[k][j])

Samme , men hver celle er nå én bit i stedet for et tall, så arbeidet per celle er bittelite. Boolske operasjoner kan dessuten pakkes mange i et maskinord og kjøres parallelt, så i praksis er den transitive tillukningen ofte raskere enn den fulle avstandsversjonen.

Hvordan velge

Velg verktøy etter grafen. Er den tynn og har bare positive vekter, vinner som regel Dijkstra fra hver node. Er den tett, eller har den negative kanter, er Floyd-Warshall det enkleste og ofte raskeste valget — én kort trippelløkke, ingen prioritetskø, og avstandsmatrisen kommer ut ferdig utfylt. Trenger du bare å vite om noder kan nå hverandre, er den transitive tillukningen den lette boolske varianten av samme idé. Felles for dem alle er kjernen: lange korteste veier bygges av kortere korteste veier, og ved å slippe til én mellomnode om gangen fyller vi inn hele kartet i .

Mine notater

Skriv egne notater for dette kapittelet. De lagres automatisk og dukker opp i «Mine notater»-oversikten paa fag-siden.

Laster…