単一始点最短路(Dijkstra法)
重みが全て\(0\)以上である任意の重み付きグラフ\(G\)における単一始点最短路は,Dijkstra法によって\(O(|E| \log |V|)\)で求めることができる.
単一始点最短路とは,あるノード\(s \in G\)から,\(G\)に含まれる任意のノードまでの最短路である.以降始点を\(s\)と呼ぶ.
説明
以下では,あるノード\(i\)について,\(s\)から\(i\)までの最短路長を\(d_{s→i}\)とする.また,あるノード\(i\)から出てあるノード\(j\)に入る(有向)辺の重みを\(e_{i→j}\)とする.
前提条件から全ての辺の重みが\(0\)以上であるため,\(s\)から到達可能な任意の\(y\)について以下の式が成り立つ.
$$d_{s→y} = min(d_{s→x} + e_{x→y}) \quad ただし \quad d_{s→x} \geq d_{s→y} \quad とする.$$
よって,\(d_{s→y}\)を小さい方から求めていくことで\(s\)を始点とする単一始点最短路を求めることができる.
単一終点最短路
\(G\)に含まれる辺の向きを全て逆向きにしたグラフを\(\hat{G}\)とし,\(i \in G\)に対応する\(\hat{G}\)のノードを\(\hat{i}\)と表す.
任意のノードについて\(s\)を終点とする最短路を求めるには,\(\hat{G}\)について\(\hat{s}\)を始点とした最短路を求めればよい.
説明
以下では,あるノード\(i\)について,\(i\)から\(s\)までの全経路の集合を\(P_{i→s}\)と表す.
任意のノード\(x \in G\)について,\(P_{x→s}\)の辺の向きを全て逆向きにした経路の集合は\(P_{\hat{s}→\hat{x}}\)と明らかに等しいため,\(s\)を終点とする最短路を求めることは\(\hat{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);
}