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

什么是二分图

今天终于复习到了二分图了。写个博客纪念一下…
二分图,额,笔者感觉有点点无聊?如果有人现在突然问我:什么是二分图?(⊙o⊙)…我也很难讲——很难专业地讲(专业的真的很恶心,反正我理解不了)…但是,我还是想跟你讲什么是二分图。下面这个,有点像小学玩过的连连看——这就是二分图。
在这里插入图片描述
当然,有专业的大神就听听二分图的定义吧:
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
就是说有一堆点可以分成两个部分(均分会好看一点),然后A部分的点连到B部分去,然后就是二分图了。用你的灵魂感受一下吧!

什么是二分图最大匹配

说得很专业对吧。然而,这还是连连看游戏。上图中,1,2,3,4,9是左边的点,5,6,7,8是右边的点。当然点和点之间都有边对吧。你的任务是:把左边的点通过这些边连到右边去。就比如说点4通过和点8的边连到了点8,然后,这就称之为4和8匹配了。
最大匹配,就是这个图里最多可以匹配多少类似这种的匹配。这个图里最多有四个匹配,就是5和1, 7和2, 6和3 , 8和4, 9匹配不了。

匈牙利算法详解

接手一个图论问题,首先怎么存图?我是用邻接表存图的,不会邻接表存图的朋友们看这里哦:邻接表存图法,很简单,你一下就可以学会的东西。记住,这是无向图,所以我们输入的时候需要一个边存两次。这是我的输入:

	cin>>p>>s;  
	cin>>nl>>nr;  //读入左点数,右点数
	for(int i=1;i<=s;i++){  
		cin>>a>>b;  
		addedge(a,b);  //双向边存2遍 
		addedge(b,a);
	} 
	for(int i=1;i<=nl;i++) cin>>lefts[i];

恩,nl和nr就是左边和右边一共的点数啦。就是那个1,2,3,4,9(左边),5,6,7,8(右边)。p是总共的点数,s是总共的边数。lefts数组是储存左边的点的,我们一会儿要从每一个左边的点开始dfs(一会儿就告诉你为啥子),所以先把这些点记录下来。
dfs的时候一个必须的工作,就是要有一个记录一个点是否被dfs过的数组。我们使用一个名为vis的一维数组记录。由于要多次深搜,所以每一次结束深搜我们都需要清空vis。那么控制深搜的代码就可以写成酱紫:

for(int i=1;i<=nl;i++){
		dfs(lefts[i]);
		memset(vis,0,sizeof(vis));
}

我们还需要两个数组cx,cy,cx[a]=b用作左边点a和右边点b匹配,那么cy[c]=d就是用作右边点c和左边点d匹配。那么我们在输出谁配谁的时候只要酱紫就可以了

for(int i=1;i<=p;i++){
		cout<<cx[i]<<"配"<<i<<endl;
}

这是效果图:在这里插入图片描述
关键就在这个深搜上。怎么个深搜法呢?
先上代码,然后我有图有真相的讲解:

bool dfs(int u){ 
	vis[u]=1;  //标记当前点已被搜过  
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(!vis[v]){
			vis[v]=1;
			if(!cy[v] || dfs(cy[v])){
				cy[v]=u;
				cx[u]=v;
				return 1;				
			}
		}
	}
	return 0;
}

vis[u]=1这没什么问题吧——标记当前点已被搜过。我们从点1开始,所以vis[1]=1
然后接下来开始邻接表遍历,为了代码简单,我特地使v=e[i].v。找到一个目标点5了!然后点5没搜过而且也没有匹配!所以就把点1匹配给点5,也就是cy[5]=1,cx[1]=5,然后return回去。
就会酱紫:
在这里插入图片描述
然后for循环就会从点2开始dfs了,于是点2dfs到了点6,而且6没有搜过且未匹配,那就匹配。在这里插入图片描述
安详退出…
接下来for循环推动点三也开始dfs了!3dfs到了6,6没有在vis里面(上一轮深搜的vis已被清空!)不幸的是,点2已经拥有了6(cx,cy数组不会清空!),于是就使用了判断if(!cy[v] || dfs(cy[v]))里的这个语句:dfs(cy[v]),是的,下一层深搜!这一层深搜,我们搜的不是点6,而是点6匹配的点——点2!也就是说,看看点2能不能连别的,能的话那点2的dfs自然就会return 1,所以点2找到了点7,太幸运了!所以2就和7匹配了,而且return 1,也就是true,告诉点3说:你可以和6匹配啦!那么点3的if(!cy[v] || dfs(cy[v]))中有了true,就允许点3和点6匹配了。
结果如下图:在这里插入图片描述
然后是从点4开始深搜了兄弟们!可怜的点2刚刚得到了7,又被点4第一个搜到了!同样,在点4的if(!cy[v] || dfs(cy[v]))判断中,对点7的匹配点——点2又开始了深搜,看看点2能不能连别的。于是点2又搜到了点6,然而点6也有自己的匹配了呢!所以又进入点二的if(!cy[v] || dfs(cy[v]))进一步搜索点3,——点6的匹配点,点3也有自己的匹配了,同时它搜到了点5,于是点5的匹配点是1,所以在点1开展搜索,然而点1除了点5什么都不连…遗憾,只能return 0了,那就一级一级return 0上去。
所以点4只好放弃了对点7的念头。然而没一会就搜到了点8!点8的条件可是好极了!既没有被搜过也没有被匹配过,纯洁如玉的少女啊!让点4夺走你的初吻吧!所以4就和8在一起了。如下图:在这里插入图片描述
最后,就是那个不甘心的9号点它只能搜到点8!然而8正和4缠绵呢,所以也是卡在了if(!cy[v] || dfs(cy[v]))这儿,顺着8的匹配它找到了4,接下来的步骤就和上一段是一样一样的了…
以左边的所有点为起点挨个儿dfs过来就这样结束了。
然后匈牙利算法也就结束了。

样例输入和完整代码

图就是上面那个。输出早就给你了,就是上面那个DOS窗口的图。什么几配几的那个。

样例输入
9 8
5 4
1 5
2 6
3 5
3 6
2 7
7 4
4 8
9 8

代码如下

#include<iostream>
#include<cstring>
using namespace std;
#define maxm 10001  //定义最大常量 
int vis[maxm];  //是否搜过 
struct edge{
	int u,v,next;  
}e[maxm];  
int head[maxm];  
int cx[maxm],cy[maxm];  //表示左、右被匹配的点
int nl,nr;  //左右点的总数 
int p,s,a,b,js;   
int lefts[maxm];  //存储左边的数 

void addedge(int u,int v){
	e[++js].u=u;
	e[js].v=v;
	e[js].next=head[u];
	head[u]=js; 
	return;
}

bool dfs(int u){ 
	vis[u]=1;  //标记当前点已被搜过  
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(!vis[v]){
			vis[v]=1;
			if(!cy[v] || dfs(cy[v])){
				cy[v]=u;
				cx[u]=v;
				return 1;				
			}
		}
	}
	return 0;
}

int main(){
	cin>>p>>s;  
	cin>>nl>>nr;  //读入左点数,右点数
	for(int i=1;i<=s;i++){  
		cin>>a>>b;  
		addedge(a,b);  //双向边存2遍 
		addedge(b,a);
	} 
	for(int i=1;i<=nl;i++) cin>>lefts[i];
	
	//执行匈牙利算法 
	int ans=0;
	for(int i=1;i<=nl;i++){
		dfs(lefts[i]);
		memset(vis,0,sizeof(vis));
	}
	for(int i=1;i<=p;i++){
		cout<<cx[i]<<"配"<<i<<endl;
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值