zoukankan      html  css  js  c++  java
  • 算法:二分图最大匹配——匈牙利算法

    一想到二分图最大匹配,我就不禁想起那几个少年抢女朋友的故事,所以今天我就来说一下二分图匹配的解决方法——匈牙利算法。

    例题

    洛谷3386 【模板】二分图匹配

    题目描述
    给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数。

    输入格式
    第一行,n, m, e。
    第二至(e + 1)行,每行两个正整数u, v,表示u, v有一条连边。

    输出格式

    共一行,二分图最大匹配。

    输入输出样例

    输入

    1 1 1
    1 1
    

    输出

    1
    

    说明/提示
    1 <= n, m <= 1000, 1 <= e <= n * m。
    因为数据有坑,可能会遇到 v>m 或者 u>n 的情况,所以请把 v>m 或者 u>n 的数据自觉过滤掉。

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

    先说最大匹配是什么:二分图最大匹配其实就是在已有边中选取尽量多的边,但得保证每个点最多出现一次。那么最大匹配数也就是能选取的最多边数,这里为了方便理解我们可以想象成n个男生去追m个女生,每个男生如果说对一个女生有好感(二者间有一条边),那么他们就有希望在一起,但是因为只能一夫一妻,所以我们希望能组成尽量多的情侣,这就是二分图最大匹配。举个例子,下图就是一个二分图的最大匹配,其中红边就是匹配边。

    在这里插入图片描述

    这里先大致说一下匈牙利算法的实现:匈牙利算法就是每次从左侧的一个点去找增广路,之后找到一条增广路径后,就将这条增广路径中所有原来的选中路径变为不选中,而原来不选中的路径改为选中。

    这里具体地解释说明一下,算法主要需要维护两个数组,我就叫opp数组和vis数组了。opp[i]表示右侧点i所连接的左侧点,就是女生i当前跟哪个男生好。vis[i]表示左侧点i是否被访问过,就是男生i是否已经找到对象。

    之后我们去枚举男生x,每次我们去选一个他感兴趣的女生,如果这个女生尚未有男生匹配,就让他俩在一起。或者如果这个女生有男生x0匹配了,那么我们就让男生x去跟当前女生匹配的这个男生x0抢,如果说当前女生匹配的男生x0还能去跟其他女生在一起,我们就让女生和当前男生x在一起,让原来女生匹配的男生x0去跟其他女生匹配。但如果说男生x0只能和这一个女生匹配,那么对不起了男生x,你只能去找其他你喜欢的女性。这里对于每个男生找女生的代码我就用递归来写了。

    bool find(int x) // 返回男生x是否可以找到心仪的女生
    {
    	if (vis[x]) return false; // 如果已经枚举过了就不用再找了
    	vis[x] = true; // 标记
    	for (int i = 0; i < (int) g[x].size(); i++) { // 枚举男生感兴趣的女生
    		int y = g[x][i]; // 获取女生y
    		if (opp[y] == 0 /*女生未被任何人匹配*/ || find(opp[y]) /*当前女生匹配的男生还能找到其他女生*/) {
        		opp[y] = x; // 使女生选择男生x
        		return true; // 返回男生x找到了心仪的女生
    		}
    	}
    	return false; // 返回男生x是个单身狗
    }
    

    这里要务必记住每次从一个男生开始找时,要将vis数组清零。还有此题有个坑,即输入的x可能大于n,输入的y也可能大于m,你省略这些不合理的数据就行了。

    最后算一下算法的时间复杂度:先得枚举男生n次,之后每次还要调用find函数,其中find最多跑m次,所以总时间复杂度为O(n * m)。

    代码

    # include <cstdio>
    # include <algorithm>
    # include <cmath>
    # include <cstring>
    # include <vector>
    
    using namespace std;
    
    const int N_MAX = 1000, E_MAX = 1000000;
    
    struct Edge
    {
    	int to, next;
    };
    
    int n, m, e;
    vector <int> g[N_MAX + 10];
    
    int opp[N_MAX + 10];
    bool vis[N_MAX + 10];
    
    void addEdge(int x, int y)
    {
    	g[x].push_back(y);
    }
    
    bool find(int x)
    {
    	if (vis[x]) return false;
    	vis[x] = true;
    	for (int i = 0; i < (int) g[x].size(); i++) {
    		int y = g[x][i];
    		if (opp[y] == 0 || find(opp[y])) {
    			opp[y] = x;
    			return true;
    		}
    	}
    	return false;
    }
    
    int hungary()
    {
    	memset(opp, 0, sizeof(opp));
    	int ans = 0;
    	for (int i = 1; i <= n; i++) {
    		memset(vis, 0, sizeof(vis));
    		ans += find(i);
    	}
    	return ans;
    }
    
    int main()
    {
    	scanf("%d%d%d", &n, &m, &e);
    	for (int i = 1; i <= e; i++) {
    		int x, y;
    		scanf("%d%d", &x, &y);
    		if (x > n || y > m) continue;
    		addEdge(x, y);
    	}
    	printf("%d
    ", hungary());
    	return 0;
    }
    
  • 相关阅读:
    C++中 destory() 和deallocate()以及delete函数的相关性和区别性
    由STL所想到的 C++显示调用析构函数
    MINIX3 内核整体架构回顾及内核定 性分析
    有一个无效 SelectedValue,因为它不在项目列表中
    SqlParameter.Value = NULL 引发的数据库异常
    前端解决跨域问题的8种方案(最新最全)
    SQL语句优化技术分析 整理他人的
    暂时未整理 已打印
    .Net_把文件数据添加到数据库中(面试题)
    ASP.NET中application对象的用法(面试题)
  • 原文地址:https://www.cnblogs.com/000zwx000/p/12509171.html
Copyright © 2011-2022 走看看