二分图匹配:匈牙利算法

前言

二分图匹配的问题应该是比较常见的吧,用匈牙利算法就可以在 O ( n , m ) O(n,m) O(n,m)的时间复杂度内解决这类问题。


二分图匹配

让我们从第一个问题开始讲起:什么是二分图

用通俗的说法,如果一张图的点集能够被分为两个部分,且没有一条边连接的两个点在同一部分,那么这就是一张二分图。

那么什么是二分图匹配呢?

如果一个边集中的任意两条边都不连向同一个节点,也就是说每个节点只连一条边,那么这个边集就是一个匹配

既然这样,那么二分图匹配就是二分图上的匹配了。


如何求解二分图最大匹配数:匈牙利算法

接下来我们要思考的问题是:如何求解二分图最大匹配数?

先来看一道模板题:【洛谷3386】【模板】二分图匹配。

想要求解这样的问题,我们就需要引入一个新的算法:由匈牙利数学家 E d m o n d s Edmonds Edmonds发明的匈牙利算法(这也是它名字的由来)。

匈牙利算法的核心思想就是让位

一起来看一张图:

二分图匹配:匈牙利算法_第1张图片

很显然,这是一张二分图。

那么这张图应该怎么用匈牙利算法来求解最大匹配数呢?我们可以一起来模拟一下匈牙利算法的求解过程。

首先,我们找到第一个右边第一个能与左边编号为1的节点匹配的节点,于是我们就找到了右边编号为1的节点:

二分图匹配:匈牙利算法_第2张图片

同理,我们为左边的2号节点找到了右边的2号节点:

二分图匹配:匈牙利算法_第3张图片

接下来轮到左边的3号节点了,但是,我们会发现第一个能与左边3号节点匹配的右边1号节点已经被匹配了。

怎么办呢?

这时,我们就要让与右边1号节点匹配的左边1号节点给3号节点让个位子出来。

于是,左边1号节点去找下一个能与左边1号节点匹配的节点,于是就找到了右边的2号节点(注意:左边1号节点并没有直接找到右边3号节点,即使3号节点是没有被匹配的)。

二分图匹配:匈牙利算法_第4张图片

可是,右边2号节点也已经被匹配了。

这时我们就需要让与右边2号节点匹配的左边2号节点给1号节点让个位子出来。但是左边2号节点已经找不到能够与它匹配的节点了,所以我们要让1号节点再去找一个能与它匹配的节点。

然后,1号节点找到了右边3号节点,而右边3号节点恰好是没有被匹配的,于是,在左边3号节点匹配完之后,就变成了这样:

二分图匹配:匈牙利算法_第5张图片

然后轮到左边4号节点了。

我们又需要让左边1号节点给4号节点让位了。

但是1号节点实在让不出位了,因此左边4号节点就找不到能够与它匹配的节点了。

综上所述,这张图的最大匹配数为3。


代码

讲了这么多,最后,我们再借助刚刚提到的那到模板题,我们一起来看一下匈牙利算法的具体实现吧:

#include
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define N 1000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
char ff[100000],*A=ff,*B=ff;
using namespace std;
int n,m,ee=0,lnk[N+5],vis[N+5],s[N+5];
struct edge
{
	int to,nxt;
}e[N*N+5]; 
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline bool GetPoint(int x,int t)//为节点x找到一个与其匹配的节点,t表示当前正在匹配的节点(记录t是因为这样就不用清空vis[]数组),返回值表示节点x能否找到一个与其匹配的节点
{
	register int i;
	for(i=lnk[x];i;i=e[i].nxt)//枚举与当前节点相邻的每一个节点
	{
		if(!(vis[e[i].to]^t)) continue;//如果一个节点已经在匹配编号为t的节点时访问过了,就跳过
		vis[e[i].to]=t;//标记该节点已经在匹配编号为t的节点时访问过了
		if(!s[e[i].to]||GetPoint(s[e[i].to],t))//如果这个节点没有节点与其匹配,或者与这个节点匹配的节点还可以找到一个新的节点与其匹配
		{
			s[e[i].to]=x;//将与这个节点匹配的节点记录为x
			return true;//找到一个与x匹配的节点,返回true
		}
	}
	return false;//找不到与x匹配的节点,返回false
} 
int main()
{
    register int i,x,y,edge_num,ans=0;
    for(read(n),read(m),read(edge_num),i=1;i<=edge_num;++i)
    {
    	read(x),read(y);
    	if(y<=m) add(x,y);
	}
	for(i=1;i<=n;++i) if(GetPoint(i,i)) ++ans;//如果这个节点能够找到一个与其匹配的节点,就将ans加1
    return write(ans),0;
}

例题

【BZOJ1433】[ZJOI2009]假期的宿舍

L i n k Link Link

【BZOJ1433】[ZJOI2009]假期的宿舍 的题解详见博客【BZOJ1433】[ZJOI2009]假期的宿舍(二分图匹配入门)

你可能感兴趣的:(匈牙利算法)