21 二分图最大匹配

文章通过过山车座位安排问题引入,解释了二分图的概念和最大匹配问题。它描述了一种通过增广路径寻找二分图最大匹配的优化策略,并给出了基于深度优先搜索的算法实现。最后,提供了一个测试案例并展示了算法的运行时间。
摘要由CSDN通过智能技术生成

 二分图最大匹配

问题描述

过山车的每一排只有两个座位,为了安全起见,是每个女生必须与一个男生坐一排。但是,每个人都希望与自己认识的人坐在一起。举个例子吧,1号女生与1号男生相互认识,因此1号女生和1号男生可以坐在一起。另外1号女生与2号男生也相互认识,因此他们也可以坐一起。像这样的关系还有2号女生认识2号和3号男生,3号女生认识1号男生。请问如何安排座位才能让最多的人满意呢?

 首先我们还是将问题模型化,如上图,左边的顶点是女生,右边的顶点是男生。如果顶点之间有边,则表示他们可以坐在一起。像这样特殊的图叫做二分图(注意二分图是无向图)。二分图的定义是:如果一个图的所有顶点可以被分为X和Y两个集合,并且所有边的两个顶点恰好一个属于集合X,另一个属于集合Y,即每个集合内的顶点没有边相连,那么此图就是二分图。对于上面的例子,我们很容易找到两种分配方案,如下。

很显然右边的分配方案更好。我们把一种分配方案叫做一种匹配。那么现在的问题就演变成求二分图的最大匹配(配对数最多)。求最大匹配最容易想到的方法是:找出全部匹配,然后输出配对数最多的。这种方法的时间复杂度是非常高的,那还有没有更好的方法呢?

我们可以这么想,首先从左边的第1号女生开始考虑。先让她与1号男生配对,配对成功后,紧接着考虑2号女生。2号女生可以与2号男生配对,接下来继续考虑3号女生。此时我们发现3号女生只能和1号男生配对(看最上面的图),可是1号男生已经配给1号女生了,怎么办?

此时3号女生硬着头皮走到了1号男生面前,貌似1号男生已经看出了3号女生的来意,这个时候1号男生对3号女生说:"我之前已经答应了与1号女生坐一起,你稍等一下,我让1号女生去问问看她能否与其他认识的男生坐一起,如果她找到了别的男生,那我就和你坐一起。"接下来,1号女生便尝试去找别的男生啦。

此时1号女生来到了2号男生面前问:“我可以和你坐在一起吗?”2号男生说:“我刚答应和2号女生坐一起,你稍等一下,我让2号女生去问问看她能否与其他认识的男生坐一起,如果她找到了别的男生,那我就和你坐一起。”接下来,2号女生又去尝试找别的男生啦。

此时,2号女生来到了3号男生面前问:“我可以和你坐一起吗?”3号男生说:“我正空着呢,当然可以啦!"此时2号女生回过头对2号男生说:"我和别的人坐在一起啦。"然后2号男生对1号女生说:"现在我可以和你坐在一起啦。"接着,1号女生又对1号男生说:"我找到别的男生啦。"最后1号男生回复了3号女生:"我现在可以和你坐在一起啦。"

最终通过这种连锁反应,配对数从原来的2 对变成了3对,增加了1对。刚才的过程叫做增广路径,不难发现如果找到一条增广路径,那么配对数将会加1。增广路径的本质就是一条路径的起点和终点都是未被配对的点

既然增广路的作用是"改进"匹配方案(增加配对数),如果我们已经找到一种匹配方案,如何确定当前这个匹配方案已经是最大匹配了呢?如果在当前匹配方案下再也找不到增广路,那么当前匹配就是最大匹配了,算法如下。

1. 首先从任意一个未被配对的点u开始,从点u的边中任意选一条边(假设这条边是u→v)开始配对。如果此时点v还没有被配对,则配对成功,此时便找到了一条增广路(只不过这条增广路比较简单)。如果此时点v已经被配对了,那就要尝试进行"连锁反应"。如果尝试成功了,则找到一条增广路,此时需要更新原来的配对关系。这里要用一个数组match来记录配对关系,比如点v与点u配对了,就记作match【v】=u和match【u】=v。配对成功后,记得要将配对数加1。配对的过程我们可以通过深度优先搜索来实现,当然广度优先搜索也可以。

2. 如果刚才所选的边配对失败,要从点u的边中再重新选一条边,进行尝试。直到点u配对成功,或者尝试过点u所有的边为止。

3. 接下来继续对剩下没有被配对的点一一进行配对,直到所有的点都尝试完毕,找不到新的增广路为止。

4. 输出配对数。

程序实现代码

#include <bits/stdc++.h>
using namespace std;

//无向连通图

int e[101][101];
int m, n;

int match[101], book[101];
int sum = 0;


int  dfs(int u) {
	for (int i = 1; i <= n; i++) {
		if (book[i] == 0 && e[u][i] == 1) {
			book[i] = 1;
			if (match[i] == 0 || dfs(match[i])) {
				match[i] = u;
				match[u] = i;
				return 1;
			}
		}
	}
	return 0;

}

int main() {
	clock_t start, finish;
	//初始化
	cin >> n >> m;

	//初始化邻接矩阵
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (i == j) e[ i][j] = 0;
			else     e[i][j] = 1e6;
		}
	//读边信息
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		e[x][y] = 1;
		e[y][x] = 1;
	}
	start = clock();
	//主程序
	for (int i = 1; i <= n; i++) {
		memset(book, 0, sizeof(book));
		if (dfs(i)) sum++;
	}
	cout<<sum;

	finish = clock();
	cout << endl << "the time cost is:" << double(finish - start) / CLOCKS_PER_SEC << endl;
	return 0;
}

测试数据

6 5
1 4
1 5
2 5 
2 6
3 4

测试结果

3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值