二分图匹配 && Hopcroft - Carp 算法详解

二分图基本概念

 

最大匹配

  集合X中有n个点,集合Y中有m个点,X与Y间共有E条边

 

 匈牙利算法 O(n*m)

  思路引入 . jpg

  每次从一个结点出发搜索匹配点,当且仅当搜到的结点未匹配或匹配后更优时,匹配。

 1 #include<cstdio>
 2 #include<string>
 3 #include<cstring>
 4 #include<vector>
 5 
 6 const int N = 1000 + 10;
 7 
 8 int n, m, e, mry[N];
 9 bool vis[N];
10 std::vector <int> Q[N];
11 
12 int read() {
13     int x = 0, f = 1;
14     char c = getchar();
15     while (!isdigit(c)) {
16         if (c == '-') f = -1;
17         c = getchar();
18     }
19     while (isdigit(c)) {
20         x = (x << 3) + (x << 1) + (c ^ 48);
21         c = getchar();
22     }
23     return x * f;
24 }
25 
26 bool DFS(int u) {
27     int size = Q[u].size();
28     for (int i = 0; i < size; ++ i)
29         if (!vis[Q[u][i]]) {
30             vis[Q[u][i]] = 1;
31             if (!mry[Q[u][i]] || DFS(mry[Q[u][i]])) {
32                 mry[Q[u][i]] = u;
33                 return 1;
34             }
35         }
36     return 0;
37 }
38 
39 int main() {
40     n = read(), m = read(), e = read();
41     for (int i = 1; i <= e; ++ i) {
42         int u = read(), v = read();
43         if (u <= n && v <= m) Q[u].push_back(v);
44     }
45     int ans = 0;
46     for (int i = 1; i <= n; ++ i) {
47         memset(vis, 0, sizeof vis);
48         if (DFS(i)) ++ans;
49     }
50     printf("%d\n", ans);
51     return 0;
52 }
匈牙利算法

 

  Hopcroft-Carp 算法 O(sqrt(n)*E)

  由于网上对HC的讲解很少,因此我想在这里讲讲HC。

  先附上代码:

 1 struct Hopcroft_Carp {
 2     int nx, ny, e, mx[N], my[N], dx[N], dy[N], dis;
 3     bool used[N];
 4     std::vector<int> G[N];
 5     
 6     void in() {
 7         nx = read(), ny = read(), e = read();
 8         for (int i = 1; i <= e; ++ i) {
 9             int u = read(), v = read();
10             if (u <= nx && v <= ny)
11                 G[u].push_back(v);
12         }
13     }
14     
15     bool BFS() {
16         std::queue<int> Q;
17         dis = INF;
18         memset(dx, -1, sizeof dx);
19         memset(dy, -1, sizeof dy);
20         for (int i = 1; i <= nx; ++ i) {
21             if (mx[i] == 0) Q.push(i), dx[i] = 0;
22         }
23         while (!Q.empty()) {
24             int  u = Q.front(); Q.pop();
25             if (dx[u] > dis) break;
26             int size = G[u].size();
27             for (int i = 0; i < size; ++ i) {
28                 int v = G[u][i];
29                 if (dy[v] == -1) {
30                     dy[v] = dx[u] + 1;
31                     if (my[v] == 0)
32                         dis = dy[v];
33                     else
34                         dx[my[v]] = dy[v] + 1, Q.push(my[v]);
35                 }
36             }
37         }
38         return dis != INF;
39     }
40     
41     bool DFS(int u) {
42         int size = G[u].size();
43         for (int i = 0; i < size; ++ i) {
44             int v = G[u][i];
45             if (!used[v] && dy[v] == dx[u] + 1) {
46                 used[v] = 1;
47                 if (my[v] != 0 && dy[v] == dis) continue;
48                 if (my[v] == 0 || DFS(my[v])) {
49                     my[v] = u, mx[u] = v;
50                     return 1;
51                 }
52             }
53         }
54         return 0;
55     }
56     
57     int Maxmatch() {
58         int ans = 0;
59         while (BFS()) {
60             memset(used, 0, sizeof used);
61             for (int i = 1; i <= nx; ++ i)
62                 if (mx[i] == 0 && DFS(i)) ++ ans;
63         }
64         return ans;
65     }
66 } HC;
Hopcroft - Carp

 

  增广路定义:依次经过 "未匹配点->未匹配边->匹配点->匹配边->匹配点->未匹配边->未匹配点……" ,以未匹配点为起点、以另一未匹配点为终点的路径。

  算法核心

    (1) 每次寻找多条增广路。(2) 在一条增广路上,易证 "未匹配边 = 匹配边 + 1"。

    增广时,只要交换增广路上的匹配边与未匹配边,就可以增加一条匹配边,减少一条未匹配边。

  BFS:寻找增广路。

以X中一个未匹配点为起点,出发寻找增广路。用 dx[] 和 dy[] 分别记录X中和Y中结点在增广路中的层次。设起点u的层次dx[u]=0,与u相连的所有结点的层次dy[v]=1,以此类推,直至找到一个未匹配点,说明成功找到了一条增广路,用dis(初始值为无穷大)来记录这个未匹配点的层次,即这个增广路的长度;如果不存在增广路(寻找结束时dis值仍为无穷大)则说明已经达到最大匹配,返回主程序。重复多个这样的循环,找到多条增广路径。

  DFS:在增广路上进行匹配尝试。(交换匹配边与未匹配边)

与匈牙利算法类似。唯一不同的是这种尝试是有方向地进行,沿着层次值+1的方向。其目的是将这条增广路上的匹配边与未匹配边交换,以增加一条匹配边。

  优化 1:匈牙利算法对集合X中的每个点都进行DFS;HC在不存在增广路时终止循环。

  优化 2:匈牙利算法在DFS(u)时,对每个能连到的点v都进行尝试;HC则巧妙设置数组 dx[] 和 dy[] 记录增广路,仅沿着增广路尝试。

  优化 3:HC在BFS中是一次找出多条不相交的增广路,以提升效率。

 

  我们用这里的图来跑一遍HC,发现最终的匹配边与匈牙利算法的不同:(数字为结点的层次值,pink边为匹配边)

             

       

  为什么会这样呢?根据两种算法的原理,易得出结论,请读者自己思考,这里不再赘述。

  为了方便理解,我们对图精简化。pink边为匹配边,yellow边为反悔边。

      

 

  图(I)表示BFS的过程,当前已经完成两对匹配,从第三个结点出发,找到一条增广路径 0->1->2->3->4->5;

  图(II)表示DFS的过程,沿增广路进行匹配尝试,比较两图容易看出匹配边与未匹配边的交换结果。

posted @ 2018-01-27 22:47  Milky-Way  阅读(856)  评论(0)    收藏  举报