二部グラフの最大マッチングを求めるアルゴリズム
アルゴリズム
二部グラフの最大マッチングを求めるアルゴリズム:
- 二部グラフ \(G=(L \cup R, E)\) に対応する以下のようなフローネットワーク \(G’\) を作る
- \(G\) にソース \(s\) とシンク \(t\) を加える
- \(s\) から \(L\) のすべての頂点に対して容量 1 の有向辺を加える
- \(R\) のすべての頂点から \(t\) に対して容量 1 の有向辺を加える
- \(L\) の頂点と \(R\) の頂点を結ぶ辺は、\(L\) から \(R\) への容量 1 の有向辺とする
- \(G’\) の最大フローを求めると、その流量は最大マッチングのサイズと一致する
※ マッチング \(M\) は、\(|M| = |f|\) となる1つの整数フロー \(f\) に対応します
※ マッチングの各ペアは、ソースからシンクへの流量1の部分的なフローに対応します
※ 最大フローを求めるアルゴリズムの代表例としては、Ford-Fullkerson法などがあります。
計算量
- \(O(|V||E|)\)
\(G’\) の最大フロー \(f^*\) の値は \(|f^*| = O(\min(L, R))\) 程度です。最大フローを求めるのにFord-Fullkerson法を用いれば、計算量は \(O(|f^*||E|)\) になります。
C++ での実装例
AOJ GRL_7_A Bipartite Matching を解くコードです。最大フローを求めるのにFord-Fullkerson法を用いています。
#include <bits/stdc++.h> using namespace std; template <class T> struct Edge { int rev, from, to; // rev:逆向きの辺の番号 T cap, original_cap; Edge(int r, int f, int t, T c) : rev(r), from(f), to(t), cap(c), original_cap(c) {} }; template <class T> struct Graph { vector<vector<Edge<T>>> G; Graph(int n = 0) : G(n) {} vector<Edge<T>>& operator[](int i) { return G[i]; } const size_t size() const { return G.size(); } Edge<T>& redge(Edge<T> e) { // 逆向きの辺を返す return G[e.to][e.rev]; // 自己ループはないと仮定(あれば G[e.to][e.rev + 1] とする必要がある) } void add_edge(int from, int to, T cap) { // 有向辺を加える G[from].push_back(Edge<T>((int)G[to].size(), from, to, cap)); G[to].push_back(Edge<T>((int)G[from].size() - 1, to, from, 0)); } }; /* FordFulkerson: Ford-Fulkersonのアルゴリズムで最大流を求める構造体 max_flow(G,s,t) :グラフGの最大流が求まる 副作用:G は最大流の残余ネットワークになる 計算量: O(|f*||E|) (f*:最大流) */ template <class T> struct FordFulkerson { const T INF = 1e9; vector<int> used; FordFulkerson(){}; T dfs(Graph<T>& G, int v, int t, T f) { // 増加可能経路を見つけて増加分のフローを返す if (v == t) return f; used[v] = true; for (auto& e : G[v]) { if (!used[e.to] && e.cap > 0) { T d = dfs(G, e.to, t, min(f, e.cap)); if (d > 0) { e.cap -= d; G.redge(e).cap += d; return d; } } } return 0; } T max_flow(Graph<T>& G, int s, int t) { T flow = 0; while (true) { used.assign(G.size(), 0); T f = dfs(G, s, t, INF); // f が増加分のフロー if (f == 0) { return flow; } else { flow += f; } } return 0; } }; int main() { int X, Y, E; cin >> X >> Y >> E; Graph<int> G(X + Y + 2); for (int i = 0; i < X; i++) { // ソース(0)から X(1~X) への辺 G.add_edge(0, i + 1, 1); } for (int i = 0; i < E; i++) { // X(1~X) から Y(X+1~X+Y) への辺 int x, y; cin >> x >> y; G.add_edge(x + 1, y + X + 1, 1); } for (int i = 0; i < Y; i++) { // Y(X+1~X+Y) からシンク(X+Y+1)への辺 G.add_edge(i + X + 1, X + Y + 1, 1); } FordFulkerson<int> ff; cout << ff.max_flow(G, 0, X + Y + 1) << endl; }
アルゴリズムの正当性
- \(G\) でのマッチング \(M\) が最大マッチング \(\Leftrightarrow\) \(M\) に対応する \(G’\) での整数フロー \(f\) が最大フロー
であることが示せれば良いです。
準備
以下の2つを用いて証明を行います。
\(G\) のマッチング \(M\) が存在するなら、\(G’\) において \(|f|=|M|\) となる整数フロー \(f\) が存在する。
逆に\(G’\) において整数フロー \(f\) が存在するなら、\(G\) において \(|f|=|M|\) となるマッチング \(M\) が存在する。
容量関数 \(c\) が整数値しか取らないなら、最大フロー \(f^*\) の値 \(|f^*|\) が整数値になり、任意の頂点 \(u,v\) について \(f(u,v)\) の値も整数値になる。
証明の概要
必要条件と十分条件に分けて示しましょう。
まずは \(G\) でのマッチング \(M\) が最大マッチングのときを考えます。\(M\) に対応する整数フロー \(f\) が存在するので、これが最大フロー \(f^*\) でないと仮定して矛盾を導けば良いです。
\(f^*\) は整数性定理から整数フローとなります。よってこれに対応するマッチング \(M^*\) を考えると、\(|M^*| = |f^*| > |f| = |M|\) となり \(M\) が最大マッチングであることと矛盾します。
逆も同様に示すことができます。
ディスカッション
コメント一覧
まだ、コメントがありません