二分图匹配——匈牙利算法

这次我写的博客跟上次的BIT是一样的,都是难度系数比较高的算法,在NOIP中很少用到,但是也不是用不到,如果有兴趣的读者可以自己来看一下,这次的这一篇呢,是关于二分图匹配的,如果想做题的话,我推荐codevs 1022 覆盖,前提是必须熟练掌握匈牙利算法。至于codevs 1232 飞行员配对呢,我建议不要去做,当一个练手的题就好了,因为那个题需要Special Judge,而出题人比较懒,没有写,所以通过率会出现极低的情况,所以我不建议死做那个题,只需要练练手即可。

那么废话不多说,开始这次的对于二分图求最大匹配的匈牙利算法的讲解:

其实有些配对问题都可以看成二分图,就是一边对着另一边,找最多能配多少个对(顶多也就是加一些细节处理和配对输出就是了),下面来讲几个概念:

设图M为图G的一个匹配:

1.交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条交错路;

2.未覆盖点:这个很好理解,就是还没有成功配对的点;

3.可增广路:如果p中有一条边<u,v>满足u和v都是未覆盖点,则称这条路径为可增广路。

显而易见,大家能想到的思路就是一遍一遍的暴力搜索,然后保留匹配数最大的那一种。但是大家有没有想过,这种算法的效率能高么。事实证明这样的时间复杂度,是以边数的指数来的,所以效率会很低。那么应该怎么办呢?那么下面我们就来学习一下匈牙利算法。

增广路的定义(也称增广轨或交错轨):
若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路的定义可以推出下述三个结论:
1-P的路径个数必定为奇数,第一条边和最后一条边都不属于M。
2-将M和P进行取反操作可以得到一个更大的匹配M’。
3-M为G的最大匹配当且仅当不存在M的增广路径。
匈牙利算法的大致轮廓:
1.先置匹配M为空;
2.找出一条增广路P,通过异或操作得到更大的M’来代替M;
3.重复2操作,直到找不到增广路径为止;
通俗一点来讲,其实就是在每次匹配的时候,去找到适合的节点,看看当前找的点还有没有与其相连的增广路径,直到再也找不到为止,然后去匹配下一个点,如果匹配不到,那么上面的一个配对过的节点就要给下面正要配对的的“腾位置”,重新来匹配别的方案;
我还是打算用一个图来让大家理解一下:
这是百度百科的图片,大家不要介意,接下来我就给大家来讲一下:
首先我们看,这是一个二分图,两边的点都有边相连,那么我们来进行匹配:
首先我们看到1和4能匹配,1和5也能匹配,那么我们先选择1和5匹配(为了讲解一下匈牙利算法……),然后我们在找到2号节点,可以发现,这时候2已经不能和5匹配,于是选择与6匹配;
接下来找到3,发现3不能匹配,于是删除2 和 6 的匹配,再次匹配2,发现2不能匹配,而1还能跟4匹配,就是在1匹配之前,选中了5,但是还有可增广路,于是删除1和5的匹配,再次匹配1
,1和4匹配,然后2和5匹配,最后3和6 匹配,这时再也没有可增广路,匹配结束。(但是事实上这个图的匹配并不是这样的,它是直接1-4,2-5,3-6,我只不过是找不到合适的图,就是拿这个图来讲解一下而已);
关于时间和空间复杂度的问题,这个很有必要说明一下,因为匈牙利算法其实也是相当于递归回溯,效率虽然比一般的搜索要高,但是其时间复杂度还是比较高的:
事实上我总是感觉使用邻接表比使用邻接矩阵的效率要高……,因为,在二分图中最多有2*n个节点,最多有n^2条边……
邻接矩阵:时间复杂度:最坏O(n^3)空间复杂度O(n^2);
邻接表:时间复杂度:O(nm),空间复杂度O(n+m)(m为二分图中的边数)
大家可以根据实际情况来进行使用。
…………………………………代码才是核心啊…………………………………………………
下面给出邻接矩阵的匈牙利算法代码(默认二分图两边的节点编号一样):
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 using namespace std;
 6 int n,m,a,b,tot = 0;
 7 bool map[101][101];//记录二分图中两边是否有边相连
 8 bool state[101];//记录二分图右半部分在匹配中是否已经选择
 9 int result[101];//记录二分图右半部分在匹配完成后,左半部分那个节点与它配对,为0的话则说明未配对成功 
10 bool find(int x){
11     for(int i = 1;i <= n;++i){
12         if(map[x][i] && !state[i]){
13               state[i] = true;//标记在本次查找中已经查找 
14               if(result[i] == 0 || find(result[i])){
15                   result[i] = x;
16                   return true;
17             }
18         }
19     }
20     return false;
21 }
22 void hungary(){
23     for(int i = 1;i <= n;++i){
24         memset(state,0,sizeof(state));//bool中的false = 0;
25         if(find(i))tot++;
26     }
27 }
28 int main(){
29     memset(result,0,sizeof(result));
30     memset(map,0,sizeof(map));
31     cin >> n >> m;//缩进是个好习惯;
32     for(int i = 1;i <= m;++i){
33         cin >> a >> b;
34         map[a][b] = true;
35     }
36     hungary();
37     cout << tot << endl;
38     for(int i = 1;i <= n;++i){
39         if(result[i] != 0) cout << result[i] << "->" << i << endl;
40     }
41     return 0;
42 }
View Code

大家可以看一下。

下面给出邻接表的匈牙利算法代码(不一定二分图两边的节点编号一样,我使用了STL中的vector模版):

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <cmath>
 5 #include <cstring>
 6 #include <vector>
 7 #define maxn 101
 8 using namespace std;
 9 vector<int>edges[maxn];
10 bool state[maxn];
11 int result[maxn];
12 int n,m,a,b,tot = 0;
13 bool find(int x){
14     for(int i = 0;i < edges[x].size();++i){
15         int o = edges[x][i];
16         if(!state[o]){
17             state[o] = true;
18             if(result[o] == 0 || find(o)){
19                 result[o] = x;
20                 return true;
21             }
22         }
23     }
24     return false;
25 }
26 void hungary(){
27     for(int i = 1;i <= n;++i){
28         memset(state,0,sizeof(state));
29         if(find(i))tot++;
30     }
31 }
32 int main(){
33     cin >> n >> m;
34     for(int i = 1;i <= m;++i){
35         cin >> a >> b;
36         edges[a].push_back(b);
37     }
38     hungary();
39     cout << tot << endl;
40     for(int i = 1;i <= n;++i){
41         if(result[i] != 0) cout << result[i] << "->" << i << endl;
42     }
43     return 0;
44 }
View Code

这次的二分图求最大匹配——匈牙利算法就讲到这里。注意,这里的增广路和网络流的增广路不要弄混,以后我会在博客中写一些网络流的东西,尤其是网络流的增广路算法。敬请期待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值