前言:
第一篇博客描述了一些与匹配相关的基本概念,下面来正式介绍匹配算法。这篇博客是比较久之前写的,主要介绍二分图最大匹配(二分图最大边独立)算法的匈牙利算法,许多匹配算法都是基于这个算法的思想进行改进的,匈牙利算法的时间复杂度大概是O( E * √V )。
简介:
匈牙利算法,由匈牙利数学家Edmonds与1965年提出,属于比较早期的一个算法。算法的核心在于寻找增广路径,是一种用增广路径求解二分图最大匹配的算法。
情景说明:
我们首先给出问题的具体模型:
有n个女生和m个男生,每个女生要选一个男生作为自己的舞伴,而且每个女生有一个或几个心仪的男生,男生没有选择权。问最多可以凑成多少对男女一起跳舞?
我们画个图举个例子,假设是4个女生和4个男生:
图中蓝色圆圈表示女生,红色圆圈表示男生,蓝色直线代表这两个人可以配对。从图中可以清晰的看到,这个图是一个二分图G,我们的目标是要找一个最大匹配M。这个最大匹配是原图的一个子图,包含所有的结点,而且每个结点的度最多为一。
算法流程:
如果没有学过网络流相关的算法的话,可能立马就会想到深搜算法了吧,但是这算法的时间复杂度是指数级别的,所以在点多的情况就必然会超时,所以我们需要一个高效的算法来解决这种问题。接下来将讲述匈牙利算法的算法流程:
1、准备工作
第一件事是建图,可以用邻接矩阵来存储;第二件事是开两个数组vis和pre,大小跟女生的人数一样即可,vis用来记录结点是否被访问过,pre数组用来保存当前的匹配情况;
2、对每个结点深搜寻找增广路径
在网络流里面,增广路径是指可以扩充当前流量的一条从源点到汇点的路径。匈牙利算法其实也是用网络流的思想来做的,这里的增广路的意义我们用个图来说明:
还是用的刚刚的图,假设我们当前匹配了两对,在图中用红色线表示匹配成对的边,接下来我们将要寻找第三个蓝结点的匹配:
我们可以看出,能和第三个蓝色结点匹配的只有第二个红色结点,然而该红色结点已经被第一个蓝色结点匹配了,如果不拆散他们,第三个结点将找不到匹配。这个时候,我们需要寻找一条增广路径:
如上图,黄色直线就代表当前一条增广路径,增广路径有几个特点:
1)长度为奇数;
2)偶数边为空闲边,奇数边为已匹配的边。
只要能找到增广路,就说明匹配还可以扩大。实际实现一般用深搜或宽搜。
3、重复第2步n次(n为女生数量)
4、得到最大二分图匹配
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,ans,a[105][105],v[105],pre[105];
bool dfs(int index)
{
for(int i=1;i<=m;i++)
{
if(!v[i]&&a[index][i])
{
v[i]=1;
if(!pre[i]||dfs(pre[i]))
{
pre[i]=index;
return 1;
}
}
}
return 0;
}
void hungary()
{
for(int i=1;i<=n;i++)
{
memset(v,0,sizeof(v));
if(dfs(i)) ans++;
}
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
memset(a,0,sizeof(a));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
ans=0;
hungary();
printf("%d\n",ans);
}
return 0;
}
算法时间复杂度: O( E * √V )