単一始点最短路(Dijkstra法)
重みが全て\(0\)以上である任意の重み付きグラフ\(G\)における単一始点最短路は,Dijkstra法によって\(O(|E| \log |V|)\)で求めることができる.
単一始点最短路とは,あるノード\(s \in G\)から,\(G\)に含まれる任意のノードまでの最短路である.以降始点を\(s\)と呼ぶ.
説明
Dijkstra法の説明と正当性の証明を同時にするような形で説明を書く.
以下の説明では,あるノード\(i\)について,\(s\)から\(i\)までの最短路長を\(d_{s→i}\)と表記する.また,あるノード\(i\)から出てあるノード\(j\)に入る(有向)辺の重みを\(e_{i→j}\)と表記する.
Dijkstra法では,\(s\)からの最短路を,最短路長が小さいノードから順に求めていく.つまり,任意の時点で,最短路が求まっているノードの集合を\(X\),最短路が求まっていないノードの集合を\(Y\)とすると,\(s\)から,\(Y\)に含まれる任意のノードへの最短路長は,\(s\)から,\(X\)に含まれる任意のノードへの最短路長以上であることが成り立つ.明らかに\(d_{s→s} = 0\)であるため,初期状態では,\(X = \lbrace s \rbrace\)である.
全ての辺の重みが\(0\)以上であるため,\(Y\)に含まれるノードの中で最も\(s\)からの最短路長が小さいノードへの最短路は,(そのノード自身と)\(X\)に含まれるノードのみから構成される.つまり,
$$min_{x \in X}(d_{s→x} + min_{y \in Y}(e_{x→y}))$$
が最小値を取るような\(x \in X, y \in Y\)のペアについて,以下の式が成り立つ.
$$d_{s→y} = d_{s→x} + e_{x→y}$$
また,明らかに
$$d_{s→y} \leq min_{p \in Y \backslash \lbrace y \rbrace}(d_{s→p})$$
が成り立つため,この\(y\)を\(X\)に含めることができる.
これを\(Y\)が空になるまで繰り返すことで,\(s\)を始点とする単一始点最短路を求めることができる.
実装
プライオリティキューを用いて,路長優先探索(造語です)をすることで求められる.計算量は,取り出しに\(O(\log |V|)\)かかるキューを用いていることから,\(O(|E| \log |V|)\)となる.
コード
pair<vector<i64>, vector<int>> dijkstra(int s, i64 n, Graph &g) {
vector<int> prev(n, -1);
vector<i64> d(n, inf);
d[s] = 0;
priority_queue<pair<i64, int>, vector<pair<i64, int>>, greater<pair<i64, int>>> que;
que.push({0, s});
while (!que.empty()) {
pair<i64, i64> p = que.top();que.pop();
int v = p.second;
if (d[v] < p.first) continue;
for (Edge &e: g[v]) {
if (d[e.to] > d[v] + e.cost) {
d[e.to] = d[v] + e.cost;
prev[e.to] = v;
que.push({d[e.to], e.to});
}
}
}
return {d, prev};
}
// 経路復元
vector<pair<int, int>> get_path(int t, vector<int> &prev) {
vector<pair<int, int>> path;
for (; prev[t] != -1; t = prev[t]) path.push_back({prev[t], t});
reverse(all(path));
return move(path);
}