6.2 Korteste vei — BFS, Dijkstra, DAG og reversert graf
Korteste-vei-verktøykassen — BFS (uvektet), Dijkstra (ikke-negative vekter), DAG via topsort, Bellman-Ford (fakta) — og det avgjørende reversert-Dijkstra-trikset.
Korteste vei er i 7 av 7 sett (100 %) — men nesten aldri som «håndkjør
Dijkstra og oppgi tabellen». Faget tester det på to helt andre måter:
- Sjanger F — matriseavkryssing, altså at du krysser av hvilken algoritme som
passer hvilken graftype. Dette er det tryggeste Del 1-poenget i hele Del 6, og
det er fire faste rader du skal kunne uten å tenke deg om.
- Sjanger H — grafalgoritme i pseudokode, altså at du selv skriver algoritmen
og oppgir kjøretiden. Her er det ett bestemt grep som skiller B fra A:
å snu grafen i stedet for å kjøre Dijkstra én gang per utgangspunkt.
Prioritet: høyeste for matrisen (du bør kunne de fire radene i søvne), og
kunne for reversert Dijkstra (nivå 2 — den er A/B-skillet, ikke
bestått-grensen).
Det er verdt å si tydelig hva som ikke kommer: du blir sjelden bedt om å føre
en Dijkstra-tabell steg for steg i et ferdig oppsett. Du blir bedt om å velge
riktig algoritme og skrive den ned. Håndkjøringen i dette kapitlet er derfor
et middel til å forstå algoritmen, ikke et mål i seg selv — den ligger i
kap. 6.5 i drillform.
Slik er kapitlet lagt opp (60 min): fire løkker med tidsanslag, og
pausepunkter etter løkke 2 og løkke 3. Dette er det lengste kapitlet i Del 6.
Sist du var her — forkunnskaper i kortform
Tre resultater fra tidligere kapitler brukes hele veien her. De står ferdig
oppfrisket, så du slipper å bla:
1. Bredde-først-søk (BFS) besøker nodene lagvis. Fra startnoden tas først alle
naboer, så alle naboers naboer, og så videre. Kjøretiden er
: hver node tas ut av køen én gang, hver kant behandles én gang.
Detaljene står i kap. 5.2.
2. Min-heapen tar ut det minste elementet i . Den er et array med
indeks fra 0; forelder til er , og barna er og
. RemoveMin koster , og det samme gjør Insert ogDecreaseKey. Se kap. 4.4. Dette er prioritetskøen Dijkstra
bruker, og hele Dijkstras kjøretid kommer derfra.
3. Topologisk sortering ordner en DAG slik at alle kanter peker framover, i
med Kahns algoritme. Se kap. 6.1. Det er
grunnen til at en vektet DAG kan løses raskere enn en generell graf.
Trenger du å friske opp hva en naboliste er, står det i
kap. 5.1.
Løkke 1 — når alle skritt koster like mye (ca. 10 min)
Et gangveinett mellom åtte bygg. Alle strekkene tar omtrent like lang tid å gå, så
det eneste som teller, er hvor mange strekk du må innom. Hvor mange strekk er
det fra bygg til bygg ?
Når alle kanter koster det samme, er korteste vei det samme som færrest kanter,
og da trenger du ingen prioritetskø. Bredde-først-søket tar nodene i den
rekkefølgen de ligger fra startnoden: først alle som er ett skritt unna, så alle
som er to skritt unna. Første gang du når en node, er det via den korteste veien —
det ligger i selve lagvis-rekkefølgen.
I en graf uten vekter (eller der alle vekter er like), gir bredde-først-søk fra
korteste avstand fra til alle andre noder, målt i antall kanter.
Hvorfor: BFS tar nodene ut av køen i ikke-synkende avstandsrekkefølge. Alle
noder med avstand 1 tas ut før noen med avstand 2, og så videre. Første gang en
node oppdages, skjer det derfor fra en node på laget rett foran — og det er en
korteste vei.
Kjøretid: . Hver node legges i køen og tas ut nøyaktig én gang,
og hver kant ses på nøyaktig én gang (to ganger i en urettet graf, én gang fra hver
ende — det endrer ikke orden).
Merk hva som gjør dette galt for vektede grafer: BFS teller kanter. En vei med
to dyre kanter er kortere i antall kanter enn en vei med tre billige, men kan
være mye lengre i kostnad. Å bruke BFS på en vektet graf er en av de faste
fellene på matrisen.
Gangveinettet har strekkene –, –, –, –, –, –,
–, – og – — alle like lange og alle toveis. Finn antall strekk
fra til hver av de andre byggene.
Køen behandles i alfabetisk rekkefølge blant like kandidater, slik at sporingen
kan gjenskapes nøyaktig.
| Steg | Tatt ut av køen | Nye noder oppdaget | Kø etter | avstand-tabell etter |
|---|---|---|---|---|
| 1 | A (avst. 0) | B = 1, C = 1 | B, C | A=0, B=1, C=1, D=∞, E=∞, F=∞, G=∞, H=∞ |
| 2 | B (avst. 1) | D = 2 | C, D | A=0, B=1, C=1, D=2, E=∞, F=∞, G=∞, H=∞ |
| 3 | C (avst. 1) | E = 2 | D, E | A=0, B=1, C=1, D=2, E=2, F=∞, G=∞, H=∞ |
| 4 | D (avst. 2) | F = 3 | E, F | A=0, B=1, C=1, D=2, E=2, F=3, G=∞, H=∞ |
| 5 | E (avst. 2) | G = 3 | F, G | A=0, B=1, C=1, D=2, E=2, F=3, G=3, H=∞ |
| 6 | F (avst. 3) | H = 4 | G, H | A=0, B=1, C=1, D=2, E=2, F=3, G=3, H=4 |
| 7 | G (avst. 3) | ingen | H | A=0, B=1, C=1, D=2, E=2, F=3, G=3, H=4 |
| 8 | H (avst. 4) | ingen | tom | A=0, B=1, C=1, D=2, E=2, F=3, G=3, H=4 |
Sluttilstand — dette er svaret du leverer:
A=0, B=1, C=1, D=2, E=2, F=3, G=3, H=4Se på steg 6 og 7. Node oppdages fra med avstand 4. Senere tas utmed avstand 3, og har også en kant til — men er allerede oppdaget, og
en ny vei via ville gitt , altså det samme. BFS ser ikke engang på
den muligheten, og trenger ikke: første gang en node oppdages, er avstanden
allerede minimal.
Kontrollen: avstandene skal stige langs køen. Ser du at en node tas ut med
lavere avstand enn den forrige, har du gjort en feil i sporingen.
Fellenote. Fella her er å bruke denne metoden på en graf med vekter. Hadde
strekket – tatt ti minutter og –– tatt tre, ville BFS fortsatt
sagt at er «nærmere» enn . BFS teller kanter, ikke minutter.
(Innstegsoppgave, sjanger H — grafalgoritme, altså at du gjenkjenner problemet og
bruker riktig algoritme.) Et uvektet nett har kantene –, –, –,
–, –, –, –, alle toveis.
a) Kjør BFS fra og oppgi avstanden til hver node.
b) Hvilken algoritme ville du brukt hvis kantene hadde hatt ulike, positive
vekter?
Løkke 2 — Dijkstra: alltid den nærmeste uferdige (ca. 18 min)
Nå koster strekkene forskjellig. Et bilnett mellom seks knutepunkter, med
kjøretider i minutter og enveiskjøring på alle strekk. Hvor lang tid tar det fra
til hvert av de andre?
Grunnideen i Dijkstras algoritme er én setning: ta alltid den uferdige noden med
lavest kjent avstand, og erklær den ferdig. Når du gjør det, kan ingen senere
oppdagelse gjøre den kortere — for enhver annen vei dit måtte gått gjennom en node
med høyere avstand, og alle kanter koster minst 0.
Den siste halvsetningen er hele forutsetningen: alle kanter må være
ikke-negative. Vi kommer tilbake til hva som skjer når de ikke er det.
sjekk om veien via er bedre enn det du har.
Blir avstanden lavere, oppdateres den, og forgjenger[v] settes til . Kostnad
per kant. De fire korteste-vei-algoritmene i dette kapitlet gjør nøyaktig
det samme — de skiller seg bare i rekkefølgen kantene relakseres i.
Finner korteste vei fra én kilde til alle noder i en graf med ikke-negative
kantvekter, ved hjelp av en prioritetskø.
Kjøretid med binær heap. Krever ikke-negative vekter;
gir gale svar hvis en kant er negativ, selv om grafen ikke har negativ sykel.
G.naboer(v) gir parene (w, vekt) for hver kant v -> w. Alle vekter erikke-negative.
avstand og forgjenger er arrayer indeksert på node.Prioritetskøen
PQ er en binær min-heap ordnet på avstand, med RemoveMin ogDecreaseKey i — se kap. 4.4.Prebetingelse: ingen kantvekt er negativ.
Postbetingelse: avstand[v] er korteste avstand fra s til v for alle v,
og for noder som ikke kan nås.
Procedure Dijkstra(G, s)
Input: vektet graf G = (V, E) som nabolister, alle vekter >= 0; startnode s
Output: avstand[v] = korteste avstand fra s til v, for alle v i V
for hver v i V:
avstand[v] = uendelig
forgjenger[v] = ingen
avstand[s] = 0
PQ = min-heap med alle noder i V, ordnet paa avstand
while PQ er ikke tom:
u = PQ.RemoveMin()
for hver (v, vekt) i G.naboer(u):
if v er fortsatt i PQ and avstand[u] + vekt < avstand[v]:
avstand[v] = avstand[u] + vekt
forgjenger[v] = u
PQ.DecreaseKey(v, avstand[v])
return avstand, forgjengerInvarianten i én setning: når en node tas ut av prioritetskøen, er avstand
til den noden endelig — ingen senere relaksering kan senke den, fordi enhver annen
vei dit må gå via en node som fortsatt ligger i køen og derfor har minst like høy
avstand.
Kjøretid: . Tell operasjonene: kall påRemoveMin, hvert , gir . for-løkka kjøres én
gang per node, og går over den nodens naboliste — til sammen ganger — og
hver relaksering kan utløse en DecreaseKey à , altså
. Summen er .
Merk at det er en sum, ikke et produkt. Å skrive
er samme telleefeil som ved topologisk sortering: den indre løkka går over nabolista
til den ene noden, ikke over alle kanter.
Kjøretidene i minutter, alle enveiskjørte: A -> B 4, A -> C 2, C -> B 1,B -> D 5, C -> D 8, C -> E 10, D -> E 2, D -> F 6, E -> F 3.
Finn korteste kjøretid fra til alle knutepunktene.
Prioritetskøen inneholder alle noder fra starten, med som avstand for
alle utenom . Kolonnen lengst til høyre viser køens innhold som heap-array
med indeks fra 0 — altså slik heapen faktisk lagrer det, ikke sortert. Element 0 er
alltid det minste; resten står i heap-orden.
| Steg | Ferdig (tatt ut av køen) | Relakserte kanter | avstand-tabell etter | Prioritetskø etter (heap-array) |
|---|---|---|---|---|
| 1 | A (avstand 0) | B: ∞ -> 4, C: ∞ -> 2 | A=0, B=4, C=2, D=∞, E=∞, F=∞ | (2, C), (∞, F), (4, B), (∞, D), (∞, E) |
| 2 | C (avstand 2) | B: 4 -> 3, D: ∞ -> 10, E: ∞ -> 12 | A=0, B=3, C=2, D=10, E=12, F=∞ | (3, B), (10, D), (12, E), (∞, F) |
| 3 | B (avstand 3) | D: 10 -> 8 | A=0, B=3, C=2, D=8, E=12, F=∞ | (8, D), (∞, F), (12, E) |
| 4 | D (avstand 8) | E: 12 -> 10, F: ∞ -> 14 | A=0, B=3, C=2, D=8, E=10, F=14 | (10, E), (14, F) |
| 5 | E (avstand 10) | F: 14 -> 13 | A=0, B=3, C=2, D=8, E=10, F=13 | (13, F) |
| 6 | F (avstand 13) | ingen | A=0, B=3, C=2, D=8, E=10, F=13 | tom |
Sluttilstand — dette er svaret du leverer:
A=0, B=3, C=2, D=8, E=10, F=13Se på steg 2. Kanten A -> B ga først avstand 4 til . Men tas ut før, og fra er det bare 1 minutt til : . Avstanden til
senkes, og
forgjenger[B] blir , ikke . Den direkte kanten er ikkekorteste vei. Det er nettopp derfor du trenger algoritmen og ikke bare kan lese
av grafen.
Se på steg 3. Nå tas ut med avstand 3. Fra dette øyeblikket er 3 endelig —
og det er trygt, fordi alt annet i køen har avstand minst 3, og ingen kant kan
trekke fra.
Korteste vei til , lest ut av forgjengerne: kom fra , fra ,
fra , fra , fra . Veien erA -> C -> B -> D -> E -> F med kostnad . Stemmer med
tabellen.
Kontrollen som fanger de fleste feil: avstandene på nodene som tas ut, skal
aldri synke. Her er de 0, 2, 3, 8, 10, 13 — stigende hele veien. Synker den, har du
enten regnet feil eller så har grafen en negativ kant.
Fellenote. Den vanligste feilen her er å avslutte for tidlig — å stoppe når
målnoden er oppdaget i stedet for når den er tatt ut av køen. I steg 2 er
fortsatt , i steg 4 er den 14, og først i steg 5 blir den 13. Sensor ber om
avstandstabellen ved terminering.
En rettet vektet graf har kantene A -> B 7, A -> C 3,C -> B 2, B -> D 4, C -> D 9, B -> E 6, D -> E 1.
a) Kjør Dijkstra fra og oppgi hele avstandstabellen.
b) Hvilken vei går korteste rute til ?
c) Hvorfor er avstanden til ikke 7?
— naturlig pausepunkt —
Du har nå to av de fire radene i korteste-vei-matrisen: uvektet gir BFS, og
ikke-negative vekter gir Dijkstra. De to siste radene handler begge om det samme
spørsmålet: hva gjør vi når en kant kan være negativ?
Løkke 3 — når en kant kan være negativ (ca. 16 min)
Negative kanter høres kunstig ut i et veinett, men de er helt vanlige så snart
vekten er noe annet enn avstand: en transaksjon som gir gevinst, et
produksjonstrinn som frigjør kapasitet, et bytte som gir tilbake mer enn det
kostet.
Og de ødelegger Dijkstra. Ikke litt — helt.
En graf har kantene A -> B 5, A -> C 6, C -> B −4 og B -> D 1. Grafen har
ingen sykel i det hele tatt, og altså heller ingen negativ sykel.
Kjør Dijkstra, kjør deretter Bellman-Ford, og forklar forskjellen.
| Steg | Ferdig (tatt ut av køen) | Relakserte kanter | avstand-tabell etter | Prioritetskø etter (heap-array) |
|---|---|---|---|---|
| 1 | A (avstand 0) | B: ∞ -> 5, C: ∞ -> 6 | A=0, B=5, C=6, D=∞ | (5, B), (∞, D), (6, C) |
| 2 | B (avstand 5) | D: ∞ -> 6 | A=0, B=5, C=6, D=6 | (6, C), (6, D) |
| 3 | C (avstand 6) | ingen | A=0, B=5, C=6, D=6 | (6, D) |
| 4 | D (avstand 6) | ingen | A=0, B=5, C=6, D=6 | tom |
Dijkstra svarer
A=0, B=5, C=6, D=6.Bellman-Ford på samme graf. Algoritmen relakserer alle kanter, gjentatte
ganger, i runder:
| Runde | Kanter som ga forbedring | avstand-tabell etter |
|---|---|---|
| 1 | A -> B: ∞ -> 5, A -> C: ∞ -> 6, B -> D: ∞ -> 6, C -> B: 5 -> 2 | A=0, B=2, C=6, D=6 |
| 2 | B -> D: 6 -> 3 | A=0, B=2, C=6, D=3 |
| 3 | ingen endring | A=0, B=2, C=6, D=3 |
Bellman-Ford svarer
A=0, B=2, C=6, D=3.Sluttilstand — det riktige svaret:
A=0, B=2, C=6, D=3Hvor Dijkstra gikk galt. I steg 2 ble tatt ut med avstand 5 og erklærtferdig. Men veien A -> C -> B koster , altså mindre. Da ble tatt
ut i steg 3, var allerede ute av køen, og relakseringen ble aldri gjort.
Feilen forplantet seg videre til : 6 i stedet for 3.
Hvorfor invarianten brøt sammen. Dijkstras garanti hviler på at ingen kant kan
trekke fra: enhver vei via noder som fortsatt ligger i køen, må koste minst like
mye som den noden du tar ut. Med en negativ kant er det ikke sant lenger — en dyr
node kan lede til en billig kant.
Merk at grafen ikke engang har en sykel. Det er en utbredt misforståelse at
Dijkstra bare feiler ved negative sykler. Én enkelt negativ kant holder.
Fellenote. Fella er å bruke Dijkstra fordi grafen «ser grei ut». Kontrollen
tar to sekunder: finnes det en negativ vekt i det hele tatt? Hvis ja, er
Dijkstra utelukket — da er svaret Bellman-Ford, eller topologisk sortering hvis
grafen er en DAG.
Finner korteste vei fra én kilde selv når negative kantvekter finnes, så lenge
grafen ikke har en negativ sykel.
Metoden er enkel: relakser alle kanter, og gjenta ganger. Etter
runde er alle korteste veier med høyst kanter funnet, og en korteste vei
kan aldri ha mer enn kanter.
Kjøretid — klart tregere enn Dijkstra, og det er prisen for å
tåle negative vekter. En ekstra runde som fortsatt gir forbedringer, avslører at
grafen har en negativ sykel.
En sykel der summen av kantvektene er negativ. Da finnes det ingen korteste
vei: hver ny runde rundt sykelen gjør veien billigere, i det uendelige.
Bellman-Ford oppdager det ved å kjøre én runde ekstra: gir runde nummer
fortsatt forbedringer, finnes en negativ sykel. I IN2010 er dette et faktapunkt,
ikke en oppgavetype.
Men det finnes en sak til, og den er den viktigste av dem alle på matrisen:
hva om grafen er en DAG?
I en DAG kan nodene ordnes slik at alle kanter peker framover. Går du gjennom
nodene i den rekkefølgen og relakserer utkantene deres, er hver node ferdig i
det du kommer til den — alt som kunne forbedret den, ligger bak deg. Da trenger du
ingen prioritetskø, og det spiller ingen rolle om noen vekter er negative.
graf gitt som nabolister. Vektene kan være negative.
avstand er et arrayindeksert på node.
Prebetingelse: er en DAG. Postbetingelse: avstand[v] er korteste
avstand fra s til alle v som kan nås, og for resten.
Procedure DAGKortesteVei(G, s)
Input: vektet DAG G = (V, E) som nabolister, startnode s
Output: avstand[v] = korteste avstand fra s til v
topo = TopologiskSortering(G)
for hver v i V:
avstand[v] = uendelig
avstand[s] = 0
for hver u i topo, i rekkefolge:
if avstand[u] er uendelig:
fortsett med neste u
for hver (v, vekt) i G.naboer(u):
if avstand[u] + vekt < avstand[v]:
avstand[v] = avstand[u] + vekt
forgjenger[v] = u
return avstandInvarianten i én setning: når u behandles i topologisk rekkefølge, er alle
noder med en kant inn til u allerede behandlet, så avstand[u] kan ikke bli
lavere senere.
Kjøretid: . Den topologiske sorteringen koster
(se kap. 6.1), og løkka etterpå går gjennom
hver node én gang og hver kant én gang. To lineære pass etter hverandre er
fortsatt lineært.
Dette er raskere enn Dijkstra — mot
— og det tåler i tillegg negative vekter. Prisen er
forutsetningen: grafen må være asyklisk.
Seks produksjonstrinn med kantene A -> B 3, A -> C 7, B -> C 2, B -> D 6,C -> D 1, C -> E −3, D -> E 2, D -> F 5, E -> F 8. Kanten C -> E er
negativ fordi trinnet frigjør lagerplass.
Finn laveste totalkostnad fra til hvert trinn.
bokstav, så grafen er en DAG, og
A, B, C, D, E, F er en lovligtopologisk rekkefølge.
Steg 2 — én gjennomgang i den rekkefølgen, med relaksering:
| Steg | Node i topologisk rekkefølge | Relakserte kanter | avstand-tabell etter |
|---|---|---|---|
| 1 | A | B: ∞ -> 3, C: ∞ -> 7 | A=0, B=3, C=7, D=∞, E=∞, F=∞ |
| 2 | B | C: 7 -> 5, D: ∞ -> 9 | A=0, B=3, C=5, D=9, E=∞, F=∞ |
| 3 | C | D: 9 -> 6, E: ∞ -> 2 | A=0, B=3, C=5, D=6, E=2, F=∞ |
| 4 | D | F: ∞ -> 11 | A=0, B=3, C=5, D=6, E=2, F=11 |
| 5 | E | F: 11 -> 10 | A=0, B=3, C=5, D=6, E=2, F=10 |
| 6 | F | ingen | A=0, B=3, C=5, D=6, E=2, F=10 |
Sluttilstand — dette er svaret du leverer:
A=0, B=3, C=5, D=6, E=2, F=10Se på . Avstanden til er 2, som er lavere enn avstanden til både(5) og (6) — noe som er umulig i en graf med bare ikke-negative vekter.
Det er den negative kanten
C -> E som gjør det: .Og se hva Dijkstra ville gjort her: den ville tatt ut … nei, den ville
først tatt ut , så (3), så (5), og deretter (6) — men i det ble
tatt ut, ble satt til 2, altså lavere enn selv. En avstand som er lavere
enn den noden vi nettopp erklærte ferdig, bryter Dijkstras hele forutsetning. På
denne grafen hadde vi vært heldige og fått riktig svar likevel, men garantien er
borte, og på grafen i eksempel 3 var svaret feil. Du kan ikke bruke «det gikk
bra sist» som argument.
Kontrollen: hver node skal ha fått sin endelige verdi før den behandles i
rekkefølgen. Det følger av topologien, og det er derfor én gjennomgang holder.
Poengtrapp-notat. På en oppgave der grafen er en DAG, er
det øverste trinnet. Dijkstra () er
korrekt hvis alle vekter er ikke-negative, men tregere, og gir færre poeng.
Bellman-Ford () er riktigst av alle i den forstand at den
alltid virker — og gir minst poeng, fordi den er den tregeste.
En vektet DAG har kantene A -> B 4, A -> C 2, C -> B −1,B -> D 3, C -> D 8, D -> E 2.
a) Oppgi en topologisk rekkefølge.
b) Finn korteste avstand fra til alle nodene, ved å gå gjennom i den
rekkefølgen.
c) Hvorfor kan du ikke bruke Dijkstra her, og hvorfor spiller det ingen rolle
at grafen ikke har noen sykel?
Dette er det tryggeste Del 1-poenget i Del 6, og det kommer som en avkryssing
(sjanger F). Lær radene som en enhet:
| Grafen er … | Bruk | Kjøretid |
|---|---|---|
| uvektet | bredde-først-søk (BFS) | |
| vektet DAG (asyklisk) | topologisk sortering, så én gjennomgang | |
| vektet uten negative kanter | Dijkstra med prioritetskø | |
| vektet med negative kanter, ingen negativ sykel | Bellman-Ford |
Slik leser du matrisen riktig. Radene er ordnet fra raskest til tregest, og du
skal alltid velge den øverste raden som passer. En vektet DAG uten negative
kanter kan løses med Dijkstra — men topologisk sortering er raskere, og
poengtrappen belønner det raskeste korrekte svaret.
De to spørsmålene som avgjør raden:
1. Har kantene vekter i det hele tatt? Nei: bruk BFS.
2. Er grafen asyklisk? Ja: bruk topologisk sortering. Nei: finnes det en
negativ kant? Nei: bruk Dijkstra. Ja: bruk Bellman-Ford.
Merk at «ingen negativ sykel» er kravet til Bellman-Ford, ikke til Dijkstra.
Dijkstra krever at ingen kant er negativ. Å blande disse to er en fast felle,
og den koster hele avkryssingen.
Fyll
inn den raskeste korrekte algoritmen og dens kjøretid for hver graftype.
a) Et uvektet sosialt nettverk.
b) En vektet, asyklisk arbeidsflyt der noen kanter har negativ vekt.
c) Et veinett med kjøretider i minutter, alle positive, med rundkjøringer
(altså sykler).
d) Et handelsnett med gevinster og tap på hver overføring, sykler finnes, men
ingen sykel gir netto gevinst.
- Å bruke Dijkstra når en kant er negativ. Dette er den dyreste feilen i
kapitlet, og den er utbredt fordi mange husker forutsetningen som «ingen negativ
sykel». Det er Bellman-Fords krav. Dijkstra krever at ingen kant er negativ,
og feiler på en graf helt uten sykler, som i eksempel 3.
- Å kjøre Dijkstra fra hvert utgangspunkt når én reversert kjøring holder.
Dette er felle #1 i bokas feilregister — full/naiv gjennomgang der ett smart
grep var mulig — og den koster typisk halve poengsummen på Del 2. Se løkke 4.
- Å bruke BFS på en vektet graf. BFS teller kanter. To dyre kanter er «kortere»
enn tre billige, målt i antall — og det er som regel feil svar.
- Å stoppe Dijkstra når målnoden blir oppdaget. En node er ferdig først når den
er tatt ut av køen. I eksempel 2 hadde verdien 14 en stund før den ble
13.
- Å bruke Dijkstra på en vektet DAG. Korrekt (hvis vektene er ikke-negative),
men tregere enn nødvendig: i stedet for
. Poengtrappen straffer det.
- Å oppgi kjøretiden for Dijkstra. Det er
telleefeilen der man ganger antall noder med antall kanter. Riktig svar er
— en sum inni en logaritmefaktor.
Og den gjennomgående: å oppgi kjøretid uten å definere størrelsene. Skriv
for antall noder og for antall kanter, og si det. Bruker du uten
forklaring, er det felle #10, som det trekkes eksplisitt for.
Løkke 4 — trikset som skiller B fra A: snu grafen (ca. 14 min)
Nå kommer det grepet som oftest avgjør toppkarakteren på en korteste-vei-oppgave.
Situasjonen er alltid den samme i formen: du har flere mulige utgangspunkt og
ett mål, og spørsmålet er hvilket utgangspunkt som er nærmest målet. En
brannstasjon som skal velges, en av flere lagre som skal levere, en av flere
inngangsdører som skal brukes.
Den nærliggende løsningen er å kjøre Dijkstra fra hvert utgangspunkt og
sammenligne. Med utgangspunkt koster det Dijkstra-kjøringer, altså
.
Den gode løsningen snur grafen. Avstanden fra til i er nøyaktig
det samme som avstanden fra til i den reverserte grafen . Så kjør
én Dijkstra, fra , i — og les av avstanden til alle utgangspunktene
på én gang.
Tre beredskapspunkter , og skal vurderes. Et oppdrag skal utføres på
sted . Veinettet er enveiskjørt med kjøretider: P -> S 4, Q -> S 2,Q -> M 7, R -> M 3, S -> T 5, M -> T 1.
Hvilket beredskapspunkt kommer raskest fram til ? Skriv en algoritme som løser
problemet generelt, med lavest mulig kjøretid.
mulige startnoder. Nøkkelobservasjonen er at «avstand fra til i » er det
samme som «avstand fra til i » — den samme veien, lest baklengs.
Antagelser om representasjon. er en rettet vektet graf gitt som
nabolister, med ikke-negative kjøretider. er mengden av
mulige utgangspunkt, og er målnoden. Vi kan bygge den reverserte grafen
ved én gjennomgang av alle nabolistene, .
Algoritmen.
Procedure NaermesteUtgangspunkt(G, U, t)
Input: rettet vektet graf G = (V, E), alle vekter >= 0;
mengden U av mulige utgangspunkt; maalnoden t
Output: den noden i U som har lavest kjoeretid til t, og selve kjoeretiden
G_R = G med alle kanter snudd
avstand = Dijkstra(G_R, t)
beste = ingen
for hver p i U:
if beste er ingen or avstand[p] < avstand[beste]:
beste = p
return beste, avstand[beste]Kjøring på tallene. Den reverserte grafen har kantene T -> S 5, T -> M 1,S -> P 4, S -> Q 2, M -> Q 7, M -> R 3. Dijkstra fra :
| Steg | Ferdig (tatt ut av køen) | Relakserte kanter | avstand-tabell etter | Prioritetskø etter (heap-array) |
|---|---|---|---|---|
| 1 | T (avstand 0) | M: ∞ -> 1, S: ∞ -> 5 | M=1, P=∞, Q=∞, R=∞, S=5, T=0 | (1, M), (5, S), (∞, Q), (∞, R), (∞, P) |
| 2 | M (avstand 1) | Q: ∞ -> 8, R: ∞ -> 4 | M=1, P=∞, Q=8, R=4, S=5, T=0 | (4, R), (5, S), (8, Q), (∞, P) |
| 3 | R (avstand 4) | ingen | M=1, P=∞, Q=8, R=4, S=5, T=0 | (5, S), (∞, P), (8, Q) |
| 4 | S (avstand 5) | P: ∞ -> 9, Q: 8 -> 7 | M=1, P=9, Q=7, R=4, S=5, T=0 | (7, Q), (9, P) |
| 5 | Q (avstand 7) | ingen | M=1, P=9, Q=7, R=4, S=5, T=0 | (9, P) |
| 6 | P (avstand 9) | ingen | M=1, P=9, Q=7, R=4, S=5, T=0 | tom |
Sluttilstand:
M=1, P=9, Q=7, R=4, S=5, T=0Svaret: er nærmest, med 4 minutter. Deretter med 7 og med 9.Kjøretid: — én reversering (), én
Dijkstra, og én gjennomgang av (, som er høyst ). Her er
antall veikryss og antall veistrekk.
Hvorfor dette er lavest mulig, og hva den naive løsningen koster. Kjører du
Dijkstra fra hvert utgangspunkt, blir det kjøringer:
. Med tre beredskapspunkter er det tre ganger
så mye arbeid; med hundre er det hundre ganger. Reverseringen fjerner faktoren
helt.
Poengtrappen på denne oppgavetypen er dokumentert eksplisitt i arkivet: den
reverserte løsningen er toppsvaret, Dijkstra-kjøringer er et korrekt
midtsvar, og det er nøyaktig den forskjellen som skiller A fra B. Felle #1
igjen: full gjennomgang der ett grep holdt.
Alternativet du også kan nevne: legg til en kunstig supernode med
kanter av vekt 0 inn til alle noder i , og kjør én Dijkstra fra . Det gir
korteste avstand fra «noe i » til i én kjøring — men det gir deg ikke
hvilket utgangspunkt som var best, uten å lese av forgjengerne. Reverseringen er
det reneste svaret her.
I den reverserte grafen fra eksempel 5 fikk viM=1, P=9, Q=7, R=4, S=5, T=0.
a) Hvor lang tid tar det fra til i den opprinnelige grafen, og
hvilken vei går ruten?
b) Et nytt beredskapspunkt legges til, med kanten N -> M av vekt 2. Blir
det nærmeste?
c) Hvorfor kan du svare på b) uten å kjøre Dijkstra på nytt fra bunnen?
En leverandør har lagre i nodemengden og skal levere til
alle kunder i nodemengden . For hver kunde vil de vite hvilket lager som er
nærmest.
a) Hva koster det å kjøre reversert Dijkstra én gang per kunde?
b) Hva koster det å kjøre Dijkstra én gang per lager?
c) Hvilken av de to velger du, og hva er betingelsen?
d) Skriv løsningen for det tilfellet at er mye mindre enn .
Marker sant eller usant, og begrunn hvert svar med én setning.
a) Dijkstra gir riktig svar så lenge grafen ikke har en negativ sykel.
b) Bellman-Ford kan brukes på enhver graf der Dijkstra kan brukes.
c) BFS gir korteste vei i en vektet graf der alle kanter har vekt 1.
d) Topologisk sortering kan brukes til korteste vei i en graf med sykler.
e) Dijkstra med binær prioritetskø er .
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har
lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder
kjernestoffet.
Kortene her er kalibrert som eksamen tester dem: algoritme, kjøretid,
forutsetning. Fordi eksamen er hjelpemiddelfri, er dette puggematerialet.
De fire faste radene, ordnet fra raskest til tregest:
- uvektet: BFS,
- vektet DAG: topologisk sortering og én gjennomgang,
- ingen negative kanter: Dijkstra,
- negative kanter, ingen negativ sykel: Bellman-Ford,
Velg alltid den øverste raden som passer.
Bellman-Fords krav.
Grunnen ligger i invarianten: når en node tas ut av køen, er avstanden endelig
fordi enhver annen vei dit må gå via en node med minst like høy avstand. En
negativ kant kan trekke fra, og da faller argumentet.
Forutsetningen er at grafen ikke har en negativ sykel. Negative enkeltkanter er
helt greit — det er hele grunnen til at algoritmen finnes. Én ekstra runde som
fortsatt gir forbedringer, avslører en negativ sykel.
En korteste vei kan ikke ha mer enn kanter — hadde den hatt flere, måtte
den besøkt en node to ganger, altså gått i en sykel, og en sykel uten negativ
totalvekt kan alltid kuttes ut.
Etter runde er alle korteste veier med høyst kanter funnet. Etter
runder er derfor alt funnet.
Sortér topologisk, gå gjennom nodene i den rekkefølgen, og relakser utkantene til
hver node. .
Metoden er raskere enn Dijkstra og tåler i tillegg negative kantvekter. Den
eneste forutsetningen er at grafen er asyklisk. Når oppgaven sier «arbeidsflyt»,
«produksjonskjede» eller «avhengigheter», er det ofte denne raden i matrisen som
gjelder.
Flere mulige startnoder, ett mål: snu alle kanter og kjør én Dijkstra fra
målet. Avstanden fra til i er den samme som fra til i .
Kjøretid mot for én
kjøring per utgangspunkt. Dette er det dokumenterte A/B-skillet på
korteste-vei-oppgaver.
Alternativ til reversering når du bare vil vite den korteste avstanden fra
noen node i en mengde : legg til en kunstig node med kanter av vekt 0
til alle noder i , og kjør én Dijkstra fra .
Kostnaden er ekstra kanter og endrer ikke kjøretidens orden. Ulempen er at
du må lese av forgjengerne for å finne ut hvilken node i som var best.
Kostnad . Hele korteste-vei-teorien er denne ene linja gjentatt i riktig
rekkefølge — BFS, Dijkstra, DAG-metoden og Bellman-Ford skiller seg bare i
rekkefølgen de relakserer kantene i.
Når en node tas ut av prioritetskøen, er avstanden til den endelig.
Begrunnelsen: enhver annen vei dit må gå gjennom en node som fortsatt ligger i
køen, og den har minst like høy avstand — pluss en kant som ikke kan være negativ.
Fjerner du ikke-negativitets-kravet, faller hele argumentet, og med det
algoritmen.
For hver node: hvilken node vi kom fra da avstanden sist ble forbedret.
Selve veien leses ut baklengs fra målet til startnoden, i . Uten dette
arrayet har du bare avstandene, ikke rutene — og oppgaver som ber om «hvilken vei»
krever begge deler.
I en uvektet graf (eller når alle vekter er like) gir BFS korteste avstand målt i
antall kanter, i .
Grunnen er lagvis-rekkefølgen: alle noder med avstand tas ut før noen med
avstand . På en vektet graf er dette galt — BFS teller kanter, ikke kostnad.
Køen svarer på «hvilken uferdig node har lavest kjent avstand». Med binær heap
koster RemoveMin og DecreaseKey hver.
Det er nøyaktig der logaritmefaktoren i kommer fra. Uten
prioritetskø, med lineær leting hver runde, blir Dijkstra — som
faktisk er raskere på svært tette grafer, men ikke er standardsvaret.
En sykel der summen av kantvektene er negativ. Da finnes ingen korteste vei — hver
ny runde rundt gjør veien billigere.
Bellman-Ford oppdager det med én ekstra relakseringsrunde: gir runde
fortsatt forbedringer, finnes en negativ sykel. I IN2010 er dette et faktapunkt på
Del 1, ikke en oppgavetype på Del 2.
Alle fire algoritmene i dette kapitlet er enkeltkilde: én kjøring gir avstanden
fra én startnode til alle andre.
Vil du ha avstanden mellom alle par, må du kjøre en av dem én gang per node.
Algoritmer laget spesielt for alle-par-problemet er ikke IN2010-pensum — se
avgrensningen i kap. 7.3.
Ser du «hvilket av flere utgangspunkt er nærmest målet», er svaret én reversert
Dijkstra, ikke én kjøring per utgangspunkt.
Beslektede formuleringer: «hvilken av stasjonene kommer først fram», «hvilket lager
skal levere», «hvilken inngang er nærmest». Alle har samme struktur: mange
startnoder, ett mål.
Skal du bare ha avstanden til én bestemt node , kan du avslutte i det blir
tatt ut av køen — da er avstanden endelig.
Å stoppe når blir oppdaget, er derimot feil: verdien kan fortsatt bli lavere.
Orden på kjøretiden endres ikke av tidlig stopp i verste tilfelle.
Noder som ikke kan nås fra startnoden, beholder som avstand gjennom hele
kjøringen.
Det er et gyldig svar, ikke en feil, og det skal stå i tabellen. En besvarelse som
utelater dem, har ikke oppgitt hele sluttilstanden — sensor ber om avstanden til
alle noder.
Samme oppgave, tre nivåer: når grafen tillater det (uvektet eller
DAG), med Dijkstra, med
Bellman-Ford.
Alle tre kan være korrekte på samme graf. Bare den øverste gir full uttelling — «å
velge riktig algoritme er å velge poeng» er den mest gjentatte poengregelen i
sensorveiledningene.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.