之前大一的时候有学姐讲过二分图匹配的匈牙利(Hungrain)算法,当时没理解。最近想补补图论,学习一下二分图匹配的匈牙利算法,其实挺简单的。
先弄清二分图匹配时重要的几个概念:
(1)交替路:从一个未匹配的顶点出发,依次经过未匹配边,匹配边...,这样由匹配边,非匹配边交替形成的路称为交替路。一定先理解这个概念,才能理解增广路的概念。
(2)增广路:从一个未匹配的顶点出发,沿交替路走,到达第一个未匹配顶点时到达终点,这个过程形成的路称为增广路。
增加匹配的关键在于在整个图中寻找增广路,如果找不到增广路,则已经找到最大匹配(Berge 定理)。否则,我们可以将增广路中所经过的边进行修改,若其原来是匹配边,则我们将其修改为未匹配边。若其原来是未匹配边,则将其变为匹配边。这样可以增加一对匹配。为什么呢?因为增广路上一定是匹配边比未匹配边少一条,可以根据定义想一想。有了这个性质,那么我们就只需要找增广路就行了。再根据Berge定理,就找到了最大匹配。看两幅图来感受一下寻找增广路增加匹配的例子:
图1 图2
我们选择从左边的顶点集出发,第一个未匹配的顶点是 4,于是我们从4开始走交替路, 4->7->1->6, 到达6时没有路可以走了,但此时没有到达未匹配顶点,因此,从4出发没有找到增广路。所以我们从5出发, 5->9->2->10,这个时候我们找到了一条增广路,把5-9, 2-10由未匹配边变为匹配边,把2 - 9由匹配边变为未匹配边,看看这个时候是不是多了一条匹配?由于左边顶点集已经没有未匹配的顶点能扩展增广路,故此时匹配已经达到最大。
hdu 2063 是一道入门级的二分图匹配。实现算法中图的存储结构选择了邻接表。
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
const int MAX = 2048;
vector<int> G[MAX];
typedef vector<int>::iterator iterator_vi;
int matched[MAX];
int vis[MAX];
int n, m, k;
bool dfs(int u)
{
for(iterator_vi i = G[u].begin(); i != G[u].end(); i++)
{
int v = *i;
if(!vis[v])
{
vis[v] = 1;
if(matched[v] == -1 || dfs(matched[v]))
{
// printf("%d %d\n", u, v);
matched[u] = v;
matched[v] = u;
return true;
}
}
}
return false;
}
int hungrain()
{
int ret = 0;
memset(matched, -1, sizeof(matched));
for(int i = 1; i <= m; i++)
{
if(matched[i] == -1)
{
memset(vis, 0, sizeof(vis));
if(dfs(i))
ret++;
}
}
return ret;
}
int main()
{
freopen("in.txt", "r", stdin);
while(~scanf("%d", &k))
{
if(k == 0) break;
scanf("%d%d", &m, &n);
for(int i = 1; i <= m; i++)
G[i].clear();
for(int i = 0; i < k; i++)
{
int a, b;
scanf("%d%d", &a, &b);
b += m;
G[a].push_back(b);
}
printf("%d\n", hungrain());
}
return 0;
}