グラフにおける橋(bridge)を検出するアルゴリズム
無向連結グラフにおいて、「取り除いたときにグラフ全体が非連結になるような辺」を橋と言います。
※「連結」とは任意の2頂点間を行き来できることを言い、「非連結」は逆に行き来できない頂点があることを言います。(くわしくはグラフを参照)
以下の図の赤い部分が「橋」の例です。
元々が連結でない無向グラフについては、「取り除いたときに非連結成分の数が増える辺」を橋とします。
アルゴリズム
単純なアルゴリズム:
- 全ての辺 e に対して以下を実行する
- e をグラフから取り除く
- グラフが非連結かを確認する(幅優先探索か深さ優先探索で確認ができます)。非連結なら e は橋
- e をグラフに戻す
※ 辺を取り除いてみてグラフが非連結になるかどうかを、全ての辺について確かめれば良いです。
Union-Find Tree を用いた単純なアルゴリズム:
- 全ての辺 e1=(u1,v1) に対して以下を実行する
- e1 以外の全ての辺 e2=(u2,v2) について以下を実行する
- u2 と v2 を Union-Find Tree 上の同じ集合にまとめる (unite)
- u1 と v1 が同じ集合になければ、e1 を除いたグラフは非連結で、e1 は橋
- e1 以外の全ての辺 e2=(u2,v2) について以下を実行する
※ Union-Find Tree についてはこちらを参照
※ こちらも辺を取り除いてみてグラフが非連結になるかどうかを、全ての辺について確かめれば良いです。
lowlink を用いた効率的なアルゴリズム:
- 適当な頂点から、DFS で以下を計算する
- ord[u] : DFS で頂点 u を何番目に探索したか
- low[u] : u からの後退辺を高々1回まで用いて到達できる頂点 w について、ord[w] の最小値
- 以下の条件を満たす辺 (u,v) が橋(bridge)
- ord[u] < low[v]
※ DFS tree (深さ優先探索木)は、DFSでグラフを探索する際に通る辺や頂点の集合のことです。これは木になっています。
※ 上述のアルゴリズムは、以下の「 (u,v) が橋となる条件」が元になっています。
- u が子として以下を満たす頂点 v を持つ
- v を根とする DFS tree の部分木において、u や u の祖先への後退辺を持つ頂点が存在しない
※ ある点 u が関節点(Articulation Points)であるときは以下のどちらかを満たします。
- u が DFS tree の根(root) で、子が2つ以上存在する
- u が DFS tree の根(root) でなく、頂点 u のある子 v について ord[u] ≤ low[v] を満たす
計算量
- 単純なアルゴリズム:O(V(V+E))
- lowlink を用いたアルゴリズム:O(V+E)
※ Union-Find Tree を用いたアルゴリズムは、O(V(V+E)) と微妙に異なりますがほとんど変わりません。
C++での実装例
struct Edge { int to; }; using Graph = vector<vector<Edge>>; using P = pair<long, long>; /* Lowlink: グラフの関節点・橋を列挙する構造体 作成: O(E+V) 関節点の集合: vector<int> aps 橋の集合: vector<P> bridges */ struct LowLink { const Graph &G; vector<int> used, ord, low; vector<int> aps; // articulation points vector<P> bridges; LowLink(const Graph &G_) : G(G_) { used.assign(G.size(), 0); ord.assign(G.size(), 0); low.assign(G.size(), 0); int k = 0; for (int i = 0; i < (int)G.size(); i++) { if (!used[i]) k = dfs(i, k, -1); } sort(aps.begin(), aps.end()); // 必要ならソートする sort(bridges.begin(), bridges.end()); // 必要ならソートする } int dfs(int id, int k, int par) { // id:探索中の頂点, k:dfsで何番目に探索するか, par:idの親 used[id] = true; ord[id] = k++; low[id] = ord[id]; bool is_aps = false; int count = 0; // 子の数 for (auto &e : G[id]) { if (!used[e.to]) { count++; k = dfs(e.to, k, id); low[id] = min(low[id], low[e.to]); if (par != -1 && ord[id] <= low[e.to]) is_aps = true; if (ord[id] < low[e.to]) bridges.emplace_back(min(id, e.to), max(id, e.to)); // 条件を満たすので橋 } else if (e.to != par) { // eが後退辺の時 low[id] = min(low[id], ord[e.to]); } } if (par == -1 && count >= 2) is_aps = true; if (is_aps) aps.push_back(id); return k; } };
グラフ上の橋の意味
グラフをネットワークとして考えると、橋はネットワーク上の脆弱性を表現しています。その辺に障害が発生して使えなくなると、ネットワークが非連結になってしまい問題となります。
よって、ネットワークの信頼性などを考慮する上で、「橋」という概念が用いられます。
ディスカッション
コメント一覧
単純なアルゴリズムの計算量、O(E(V + E)) じゃないですか?