Prerekvizity: [L2.3] Relaxace hrany. Tam jsme viděli, že všechny algoritmy kapitoly skládají týž krok — relaxaci — a liší se jen strategií pořadí. Dnes první a nejslavnější strategie: Dijkstra [1959].
Z úlohy T01 v [L2.3] víš, že dobré pořadí relaxací ušetří celé průchody. Ideál by byl relaxovat odchozí hrany každého vrcholu právě jednou — a to jde jen tehdy, když vrchol expanduju až ve chvíli, kdy už se jeho značka nikdy nezmění.
U toho s nejmenší značkou $l(v) = \min_{i \notin R} l(i)$ (kde $R$ je množina už uzavřených vrcholů). Proč: kdyby do $v$ vedla ještě kratší cesta, musela by někde poprvé opustit $R$ — přes nějaký neuzavřený vrchol $k$. Jenže ten má značku $l(k) \ge l(v)$ a zbytek cesty z $k$ do $v$ jen přidává nezáporné délky. Kratší to tedy být nemůže.
Přesně tohle je Dijkstrův nápad: vrchol s nejmenší značkou smím uzavřít (prohlásit značku za definitivní) a jeho odchozí hrany relaxovat — každou jen jednou za celý běh.
(Tohle je intuice; poctivý indukční důkaz korektnosti patří do kapitoly K3 — tady v K2 nás zajímá běh a použití.)
Input: digraph $G$, weights $c : E(G) \to \mathbb{R}^+_0$ and node $s \in V(G)$.
Output: Vectors $l$ and $p$. For $v \in V(G)$, $l(v)$ is the length of the shortest path from $s$ and $p(v)$ is the previous node in the path. If $v$ is unreachable from $s$, $l(v) = \infty$ and $p(v)$ is undefined.
l(s) := 0; l(v) := ∞ for v ≠ s; R := ∅;
while R ≠ V(G) do
Find v ∈ V(G) \ R such that l(v) = min_{i ∈ V(G) \ R} l(i);
R := R ∪ {v} // in the first run we add vertex s
// further calculate non-permanent value of l(w) for
// every node on border of R
for w ∈ V(G) \ R such that (v, w) ∈ E(G) do
if l(w) > l(v) + c(v, w) then
l(w) := l(v) + c(v, w); p(w) := v;
end
end
end
Vnitřní if je doslova relaxace z [L2.3]. Výběru vrcholu $v$ a relaxaci jeho odchozích hran se říká expanze vrcholu $v$ — na pořadí expanze se zaměří hned další lekce [L2.5]. Množina $R$ obsahuje uzavřené (permanent) vrcholy: jejich $l$ už je výsledná vzdálenost.
Proklikej si celý běh ze startu $s$ — sleduj, jak se vrcholy uzavírají v pořadí rostoucí značky a jak se značky na hranici $R$ ještě opravují:
Není. Trvalá je značka až okamžikem uzavření (přidání do $R$). Dokud je vrchol venku, jeho značka je obyčejný pracovní odhad z [L2.3] a smí klesat — to se stalo $b$ při expanzi $a$. Slide 16 tomu říká non-permanent value on border of R: hranice $R$ má rozpracované odhady, vnitřek $R$ hotové vzdálenosti.
Všimni si pořadí uzavírání: $s\,(0),\ a\,(2),\ b\,(4),\ c\,(5)$ — značky uzavíraných vrcholů neklesají. To je charakteristický rys Dijkstry a budeme ho potřebovat v [L2.5].
Minimum $\infty$ znamená, že do žádného zbylého vrcholu jsme zatím neviděli cestu — a protože expandované hrany vedou jen z $R$, už ani neuvidíme. Takové vrcholy jsou nedosažitelné: uzavřou se s $l(v) = \infty$ a $p(v)$ nedefinovaným, přesně podle výstupní specifikace slidu 16. (Relaxace z nich nic neudělají: $\infty + c$ nic nepodleze.)
A pokud hledáme jen cestu $s \to t$: jakmile se $t$ dostane do $R$, jeho značka je definitivní — algoritmus lze ukončit v okamžiku zařazení $t$ do $R$ (slide 21). Zbytek běhu by počítal vzdálenosti, které nepotřebujeme.
Stačí tři vrcholy: $s \to a$ s váhou $1$, $s \to b$ s váhou $2$ a $b \to a$ s váhou $-2$. Skutečná nejkratší cesta do $a$ vede přes $b$ a má délku $0$. Dijkstra ale uzavře $a$ hned ve druhé iteraci se značkou $1$ — minimum mezi neuzavřenými — a tu už nikdy nepřepíše. Proklikej:
Krokovaný protipříklad (pozor, spoiler — nejdřív zkus najít vlastní graf na papír):
Dijkstra je tzv. label-setting algoritmus (slide 20): v každé iteraci se jedna značka $l(v)$ stane trvalou (optimální). Naproti tomu label-correcting algoritmy (např. Bellman-Ford [L2.12] nebo Floyd [L2.14]) považují všechny značky za dočasné až do úplně posledního kroku, kdy se trvalými stanou všechny najednou — přesně jak sliboval výhled v [L2.3].
Časová složitost Dijkstry je $\mathcal{O}(n^2)$, resp. $\mathcal{O}(m + n \log n)$ s prioritní frontou (datová struktura, která umí rychle vydávat minimum; $n = |V(G)|$, $m = |E(G)|$). Pro speciální grafy existují i lineární algoritmy — mj. pro DAGy, kde pořadí relaxací dostaneme zadarmo z topologického uspořádání [L2.13].
You are given the directed graph with 8 nodes below. Run Dijkstra's algorithm from node $1$ and create two vectors $l$ and $p$, where vector $l$ contains the lengths of the shortest paths from the starting node and vector $p$ contains the predecessor of each node. Perform 8 iterations of the algorithm and write out the vectors after each iteration. In addition, write out the order in which the nodes were added to the set $R$.
Orientovaný graf s 8 vrcholy a nezápornými váhami, start ve vrcholu $1$. Máme oditerovat Dijkstru: po každé z 8 iterací (každá uzavře jeden vrchol) vypsat aktuální vektory $l$ a $p$ a nakonec uvést pořadí uzavírání. Pozor na vrchol $8$ — nevede do něj žádná hrana.
Veď si tabulku: řádek = iterace, sloupce = $l(1), \dots, l(8)$, a zvlášť si značkuj, kdo už je v $R$. V každé iteraci nejdřív vyber minimum mezi neuzavřenými (zapiš si pořadí!), pak relaxuj jen jeho odchozí hrany do neuzavřených vrcholů. Nezapomeň: při rovnosti $l(w) = l(v) + c(v,w)$ se nepřepisuje nic — test je ostrý. Hodnoty $\infty$ nech $\infty$, dokud je relaxace skutečně nesníží.
Init: $l = (0, \infty, \infty, \infty, \infty, \infty, \infty, \infty)$, $R = \emptyset$. Zápis $l$ vždy po dokončení iterace; změny tučně.
Výsledné vektory:
$$l = (0,\, 4,\, 1,\, 6,\, 3,\, 7,\, 9,\, \infty), \qquad p = (-,\, 1,\, 1,\, 2,\, 3,\, 5,\, 6,\, -).$$
Pořadí přidávání do $R$: $1, 3, 5, 2, 4, 6, 7, 8$ — značky $0, 1, 3, 4, 6, 7, 9, \infty$ neklesají ✓. Kontrola cestou: couváním ze $7$ po $p$: $p(7)=6$, $p(6)=5$, $p(5)=3$, $p(3)=1$ → nejkratší cesta $1 \to 3 \to 5 \to 6 \to 7$ délky $1+2+4+2 = 9 = l(7)$ ✓.
Krokovaná verze řešení (pozor, spoiler — nejdřív si to zkus na papír):