Problem: Givet en graf (riktad eller oriktad) där varje båge har en viss längd, och två noder s och t, hitta den kortaste vägen från s till t.
Att hitta kortaste vägen mellan två punkter är ett problem som givetvis uppkommer i transportsammanhang men även inom t.ex. bildbehandling. På tävlingar kommer säkert inte problemet i ren form, men det skulle kunna vara en del av en större lösning. Lyckligtvis är problemet lätt att lösa. Den beskrivna metoden kallas för Dijkstra's algoritm.
Först ett specialfall: alla bågar har längden 1. Då ges lösningen direkt av bredden-först-sökning (BFS) , med noderna som situationer och s som startsituation. Dijkstra's algoritm är väldigt lik BFS, och därför förutsätts här att du vet hur denna sökning fungerar.
Antag att du är mitt i BFS-sökning av en graf. Du tar fram en nod A markerad med 3 "drag" ur kön. I detta sammanhang betyder detta att det kortaste avståndet mellan s och A är 3. Sedan gör du alla möjliga drag, i detta fall undersöker du alla intilliggande noder. Om någon nod B inte redan är markerad, innebär det att du automatiskt vet att avståndet från s till B är 4, för om avståndet vore <= 3 skulle noden redan vara markerad eftersom alla 3-noder hittas innan 4-noderna. (Däremot kan det finnas någon annan väg som också ger avståndet 4, men då spelar det ju ingen roll vilken vi väljer.) Vi kan också lugnt placera B sist i kön eftersom den då hamnar efter alla 3-noder och före alla framtida 5-noder.
Om bågarna inte har längden 1 blir det lite annorlunda. Vi kan inte längre vara säkra på att vi direkt har hittat kortaste avståndet till B. Om t.ex. längden av bågen A -B = 3, blir ju avståndet från s till B = 6. Men kanske hittar vi strax efteråt en annan 3-nod som också har en båge till B med längden 2. Då blir det den kortaste vägen från s till B (avstånd 5). Tydligen är det så att när vi först hittar en väg till B är det en möjlig kortaste väg, men vi kan inte veta säkert. När kan vi bli säkra?
Antag att du markerar med svart de noder som du är säker på kortaste vägen till, i detta exempel s med avståndet 0 samt A och C med avståndet 3. Titta nu på alla bågar som kopplar ihop en svart nod med en vit (A-B, C-B , C-D). Välj den av dessa som ger det kortaste avståndet från s till den vita noden (C-B ger avståndet 3 + 2 = 5 från s till B) och markera B som svart med avståndet 5. Vi kan nämligen vara säkra på att det inte finns en kortare väg till B, eftersom en sådan väg skulle gå från någon av de svarta noderna till en annan vit och då automatiskt bli längre. För varje nod vi markerar med svart är vi ett steg närmare lösningen. I exemplet ovan blir ordningen: B (avstånd 5), F (6), D (7), E (10), t (12)
Ett enkelt sätt att hålla reda på de "svart-vita"
bågarna visas i nedanstående algoritm. Varje gång man
svartmarkerar en
nod, undersöker man dess vita grannar för att se om det
går att hitta bättre vägar till dessa via den nu
svartmarkerade noden. Första gången en nod
påträffas handlar det ju om att det finns en väg dit
överhuvudtaget, men detta specialfall behöver man inte bry
sig om om man sätter alla avstånd i början
till ett stort värde (helst oändligheten).
Exempel:
När F hittas sätts dist[t] till 6 + 9 = 15,
eftersom 6 + 9 < 1000000 (som var ursprungsvärdet för dist[t]
).
Däremot behåller dist[D] värdet 7, eftersom 6 +
3 inte är < 7
När E hittas sätts dist[t] till 10 + 2 = 12
eftersom 10 + 2 < 15
Det effektivaste sättet att välja det minsta dist-värdet bland de vita noderna, är att spara dist-värdena i en prioritetskö, som implementeras med en heap . Oftast räcker det dock att ha en array som man letar igenom varje gång. Algoritmen blir ändå bara O(E*V) där V är antalet noder och E är antalet bågar.
Det som blir enkelt när man använder en array är just
när
en bättre väg hittas, alltså när ett
dist-värde
måste uppdateras. Om man har en array är det bara att
ändra
värdet på dist[v]. Eftersom man sedan letar igenom alla
noder
för att ta den minsta kommer man att hitta v ifall v har det
minsta
dist-värdet. Om man använder en prioritetskö (heap)
utnyttjar
man heapens struktur för att bara behöva tiden O(log V)
varje gång man tar den nod med minst dist-värde.
Därför
måste man uppdatera heapen varje gång ett dist-värde
minskas,
den noden ska flyttas fram i kön. Detta görs genom att ha
någon
slags "pekare" från noden till heapen så att det lätt
går
att hitta dess plats. Vet man nodens plats är det bara att
göra
en INSERT
-liknande procedur som flyttar noden uppåt i heapen (framåt
i
kön). Det blir alltså lite krångligare, men det kan
vara
nödvändigt i problem med många noder, eftersom
algoritmen
med heap blir O(E*log V).
Shortest_Path(s, t)
black[1..numNodes] := false
dist[1..numNodes] := 1000000 Stort värde - betecknar ej hittad nod
dist[s] := 0 Startnoden
har avståndet 0
while (vita hittade noder existerar)
do
u := den vita nod
(d.v.s. black[u] = false) som har minst dist-värde
black[u] := true Markera u med svart
for e := (alla
bågar från u) do
v := den andra noden till båge e
if (not black[v]) and (dist[u] + length[e] < dist[v]) then
dist[v] := dist[u] + length[e]
En bättre väg till v är hittad
Flytta v framåt i prioritetskön
Om heap används
if dist[t] = 1000000 then
**** Omöjligt
att komma från s till t ****
else
**** Kortaste
vägen har längden dist[t] ****
Alla kortaste vägar
Ibland vill man inte bara veta kortaste vägen mellan s och t, eller mellan s och alla andra noder (detta ges också av Dijkstra's algoritm), utan mellan alla kombinationer av två noder. Man kan givetvis köra Dijkstra's algoritm numNodes gånger med olika noder som startnod, men det finns en algoritm som är ännu kortare att skriva. Den är så enkel att det till och med kan löna sig att använda den för att hitta bara en kortaste väg, om man vet att man har gott om exekveringstid. Komplexiteten är O(n^ 3) med avseende på antalet noder.
All_Shortest_Paths (Floyd-Warshall's algoritm)
Skapa en matris W[1..numNodes, 1..numNodes]
där
W[u, v] = bågens
längd om det finns en båge från u till v, annars
1000000 .
for a := 1 to numNodes do
for b := 1 to numNodes do
for c := 1 to numNodes do
if W[b, a] + W[a, c] < W[b, c] then
W[b, c] := W[b, a] + W[a, c]
**** Kortaste vägen från s till t: W[s, t] ****
Lägg märke till att if-satsen gör precis samma sak som i Shortest_Path: kollar om det finns en bättre väg mellan b och c via a.
Tävlingsuppgifter
Problem | Tävlingens hemsida | Hjälp och kommentarer |
Galactic Import (388) | Problem Set Archive | |
The Doors (393) | Problem Set Archive | |
City navigation (176) | Problem Set Archive |