匈牙利算法

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

我是在下面这篇博客里自学的,里面把相关概念和算法都讲得十分清楚。
实在看不懂再回来看我的低配版吧。
Renfei Song’s Blog

预备知识

引用自👆博客

二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。
在这里插入图片描述

匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。
在这里插入图片描述

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
在这里插入图片描述

匹配边即连接两个匹配点所用的边。
没有被用来匹配的边就是非匹配边。

交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。
在这里插入图片描述

个人理解是一定要从非匹配边开始,非匹配边结束,因为其中非匹配边和匹配边交替出现。
所以总边数必然是奇数,且非匹配边比匹配边多一条

增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)


算法理解

化为寻找增广路径总数

将二分图分为左右两个集合。
由于增广路的特性——先走未匹配边,后走匹配边,依次轮换。所以每次从左边的集合中选一个点找增广路,终点必然在右边的集合中,并且非匹配边比匹配边数多一条。
核心:找到增广路后,只需要将增广路径上的边反转(匹配边和非匹配边反转)即可让匹配数加一。
至此,二分图的最大匹配问题可以完全转化为寻找增广路总数的问题。

寻找增广路径总数呢

实际上bfs和dfs均可实现 ,目前只学了代码量较少的dfs方法。
由增广路特性可知,从左集合走到右边集合必然使用一条非匹配边。从右集合走到左集合必然使用一条匹配边。
所以用一个数组记录右集合点i的匹配左集合点j match[i] = j;
为了防止死循环所以用集合vis[]来标记被匹配过的右集合点。

增广路从一个新的(只要正常枚举左端点就能保证当前端点都没有被匹配过)左端点开始。
当前点在左集合时,枚举能走到的右集合点看是否能匹配。若能则直接匹配上,结束。若右集合的点已经被匹配则延长交替路,通过match数组找回左集合点。一直循环直到找到一个未匹配的右端点时结束。


代码实现

vector<int>vec[maxn];//用于存放左集合点能连接到的点。
bool vis[maxn];//用于防止递归死循环。
int match[maxn];//用于记录右集合的匹配点。

bool dfs(int x)
{
    for (int i : vec[x]) { // 当前点的每个邻接点
        if (!vis[i]) {     // 要求不在当前交替路中
        //要注意的是vis数组会在枚举左集合点的时候重置为false
            vis[i] = true; // 放入交替路
            if (match[i] == 0 || dfs(match[i])) {
                // 如果右集合的点未匹配,说明交替路为增广路,则记录匹配,并返回成功
                match[i] = x;
                return true;
            }
        }
    }
    return false; // 不存在增广路,返回失败
}

##例题
hdu2063
参考代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 505;

vector<int>vec[maxn];
bool vis[maxn];
int match[maxn];

bool find(int x){
	for(int i : vec[x]){
		if(!vis[i]){
			vis[i] = 1;
			if(match[i] == 0 || find(match[i])){
				match[i] = x;
				return true;
			}
		}
	}
	return false;
}

int main(){
	int k;
	while(cin >> k && k){
		int n, m; cin >> n >> m;
		for(int i = 1 ; i <= n ; i++)vec[i].clear();
		memset(match, 0, sizeof match);
		int u, v;
		for(int i = 1 ; i <= k ; i++){
			cin >> u >> v;
			vec[u].push_back(v);
		}
		int ans = 0;
		for(int i = 1 ; i <= n ; i++){
			memset(vis, 0, sizeof vis);
			ans += find(i);
		}
		cout << ans << endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值