【算法学习笔记】26:匈牙利算法(二分图最大匹配)

2023-10-27

1 简述

给定一个二分图,例如:
在这里插入图片描述

匈牙利算法能够快速的计算出一种匹配方式,使得匹配的数量最多。注意,一个成功的匹配方式中,没有两条边是共用了同一个点的。

形象的说,这个问题可以理解成二分图两边分别是男生和女生,有连线的表示可以凑成一对,匈牙利算法就是用来计算最多能够凑成多少对(不存在脚踏多条船的情况)。


例如左边是男生,右边是女生,可以任选一方为主动方,比如是男生方,那么流程如下。

对于每个男生结点,对所有与之有连线的女生结点,检查对应的女生是不是单身,如果是就直接凑成一对。那么在上图的例子中,前两个男生都可以直接匹配到自己连线的第一个女生:
在这里插入图片描述

对于男生 3 3 3而言,他所能匹配的第一个女生是 2 2 2,但是这个女生已经是非单身了。这个时候就要去不断尝试,尝试让女生 2 2 2已经匹配的男生 1 1 1换一个匹配的女生(而不会让当前已经成对的匹配数量减少)。

接下来男生 1 1 1检查自己所能匹配的下一个女生 4 4 4,这个女生是非单身,所以将男 1 1 1与女 4 4 4匹配,此时女 2 2 2被释放出来,得以和男 3 3 3匹配:
在这里插入图片描述

接下来检查下一个男生 4 4 4,它所能匹配的女生 3 3 3是单身,将他俩匹配:
在这里插入图片描述

至此,能匹配的都匹配上了,这个二分图的最大匹配数量是4。

2 模板题:二分图的最大匹配

求最大匹配的时候,可以直接存到邻接表里,因为遍历的时候是对每个男生遍历所有能匹配的女生,所以只要存一下从男生到女生的边,不需要像存普通无向图那样存双向边。

在实现时要注意,int match[]数组用来记录每个女生当前匹配的男生是哪一个(存标号),如果单身里面存的就是0

由于在匈牙利算法中,即使一个女生已经有匹配了,也可能被更换匹配,所以还需要每次清空一个bool st[]数组,来记录当前给某个男生找匹配的过程中,每个女生有没有遍历过,防止出现死循环。

#include <iostream>
#include <cstring>

using namespace std;

// 由于只存单项,边M不用开两倍
const int N = 510, M = 1e5 + 10;

// 邻接表
int h[N], e[M], ne[M], idx;
void add_edge(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

// match记录女生当前匹配的男生
int match[N];
// st记录当前这轮每个女生有没有遍历过
// st[j] = true时候,j这个女生是被禁用的
bool st[N];
// 匈牙利算法,尝试给x找一个女朋友
bool find(int x) {
    // 对于能够和x匹配的所有女生j
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        // 如果j没有被禁用(没有遍历过)
        if (!st[j]) {
            // 就将其锁定,因为要尝试让x匹配j
            st[j] = true;
            // 如果j本来就是单身,或者j的男朋友能换一个女朋友
            if (match[j] == 0 || find(match[j])) {
                // 就将x匹配上j
                match[j] = x;
                // 因为x能找到女朋友,所以返回true
                return true;
            }
        }
    }
    // 尝试了x的所有能匹配的j都不行,就返回false
    return false;
}

int main() {
    // 读取二分图a->b
    memset(h, -1, sizeof h);
    int n1, n2, m;
    cin >> n1 >> n2 >> m;
    for (int i = 0; i < m; i ++ ) {
        int a, b;
        cin >> a >> b;
        add_edge(a, b);
    }
    // 遍历每个男生尝试匹配
    int res = 0;
    for (int i = 1; i <= n1; i ++ ) {
        memset(st, false, sizeof st);
        // 每次尝试都会尝试让i匹配进来
        // 结果res是只增不减的
        if (find(i)) res ++ ;
    }
    cout << res << endl;
    return 0;
}

特别要注意每轮要清空st数组,在这一轮中,尝试让女生j匹配给男生x时,就要设定st[j] = true以将j锁定了,被锁定的女生j永远不会解锁。

匈牙利算法基于贪心的思想,理论时间复杂度是多项式级别的 O ( n m ) O(nm) O(nm),但是实际运行速度还是比较快的。

3 Java实现

import java.util.*;


public class Main {
    private static int h[], e[], ne[], idx;

    private static void add_edge(int a, int b) {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }

    private static int match[];
    private static boolean st[];

    private static boolean find(int x) {
        for (int i = h[x]; i != -1; i = ne[i]) {
            int j = e[i];
            if (!st[j]) {
                st[j] = true;
                if (match[j] == 0 || find(match[j])) {
                    match[j] = x;
                    return true;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n1 = scanner.nextInt();
        int n2 = scanner.nextInt();
        int m = scanner.nextInt();

        h = new int[n1 + 1];
        for (int i = 1; i <= n1; i++)
            h[i] = -1;
        e = new int[m];
        ne = new int[m];

        for (int i = 0; i < m; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            add_edge(a, b);
        }

        match = new int[n2 + 1];
        st = new boolean[n2 + 1];

        int res = 0;
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++)
                st[j] = false;
            if (find(i))
                res++;
        }
        System.out.println(res);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【算法学习笔记】26:匈牙利算法(二分图最大匹配) 的相关文章

  • 1600*B. Jumping Jack(数学&&找规律)

    解析 一直往右条 直到第一次超过 x 如果当前和目标点 p x为偶数 则 p x 2 的那一步向左跳 这样会少跳 p x 正好补在多跳的这一段 如果为奇数 则不能除2 则继续跳 直到距离为偶数即可 x和x答案一样 include
  • 数据结构 图 part2

    文章目录 图的遍历 深度优先遍历 DFS 遍历步骤 邻接矩阵的存储 邻接表的存储 广度优先遍历 BFS 遍历步骤 非连通图的遍历 连通分量 如何遍历 生成树 图的遍历 深度优先遍历 DFS 遍历步骤 在访问图中某一起始顶点v后 由v出发 访
  • 图论感想

    图论基础无非也就是图存储 遍历 有向图无向图的连通性 分为图联通和联通分量 有向图为强联通分量 割点与割边 本人目前还没有看网络流内容 只是大致知道是什么 觉得也是图论一部分 个人认为学东西应该大体了解一下所学内容 每学一个必要好好思考 最
  • XYZZY 【POJ - 1932】【SPFA】

    题目链接 有N个点 然后输入1 N个点 输入从它到其他点的血量变化 然后有几个点能到达 最后是这几个点 我们起点为1 终点为N 然后求的是我们是不是有可能或者达到终点 gt 0 直接SPFA跑最长路 感觉是在造样例 6 0 1 2 1000
  • 无向图的遍历-BFS与DFS

    一 理论部分 无向图的遍历可用深度搜索 DFS 与广度搜索 BFS 深度搜索的基本方式是由图的一个节点1出发然后随机选一个与其相邻的节点2 接着在选择一个与其相邻的节点3 当一条路遍历完后再选择最近一个遍历过的 且相邻节点有未遍历过的节点
  • Codeforces Round 867 (Div. 3)(A题到E题)

    链接 Dashboard Codeforces Round 867 Div 3 Codeforces 头一次div3做出来四题 第五题也差临门一脚 赛后看到别人e题跟自己几乎一样的思路肠悔青了 还得练才行 A TubeTube Feed 签
  • 图论17(Leetcode864.获取所有钥匙的最短路径)

    用二进制表示获得的钥匙 假设n 钥匙个数 000000000代表没有钥匙 0000000001代表有idx为1的钥匙 0000000011代表有idx 1 2的钥匙 这方法巧妙又复杂 代码 class Solution static int
  • 西安电子科技大学第二届程序设计新生赛-F-zxy的长跑【欧拉回路】

    题目链接 好极了的欧拉回路 我们想知道怎样才能跑完所有的边 我们可以从度为奇数的点出发 因为这是欧拉回路的无向图的先觉调节 当然 这道题还有另外一种可能就是这是一个环 1 gt 2 2 gt 3 3 gt 4 4 gt 1 那么就没有奇数度
  • Codeforces-1454E Number of Simple Paths(基环树-思维)

    题目大意 给你n个点 n条边 求图中简单路径的个数 题目思路 n个点n条边 那么图中一定有一个环 拿这个图来讲 我们将两点间的关系分为4种 1 两点都在环上 简单路径的个数为2 例如2与5 2 一个点在环上一个点不在环上 简单路径个数为2
  • 2021级新生个人训练赛第38场

    问题 A chicken 题目描述 小 x 非常喜欢小鸡翅 他得知 NSC 超市为了吸引顾客 举行了如下的活动 一旦有顾客在其他超市找到更便宜的小鸡翅 NSC 超市将免费送给顾客 1000g 小鸡翅 小 x 为了尽可能的省钱 走遍了各大超市
  • Even Degree【2020 年 “游族杯”E题】【欧拉回路】

    题目链接 题意 有N个点 M条边 每次可以删去一条两端点的度不都是奇数的边 问最多可以删除几条边 题目保证初始所有点度为偶数 首先 题目保证了初始的时候所有的点的度都是为偶数的 于是原图中的每一个联通块一定是一个欧拉回路 对于欧拉回路 最好
  • 树形dp(例题)

    树的最长路径带权值 树的直径可能时红色的边 从上图可以看出 每次要两个变量存放以u为根 最长路径d1 和次长路径d2 那么整个树的最长路径就有可能是d1 d2 我们每次要返回以u为根的贯穿试的最长路径 给他的父节点判断使用如下图 inclu
  • 离散数学第一章总结

    离散数学第一章 1 公式类型 1 重言式 也是永真式 公式真值恒为1 2 矛盾式 永假式 真值恒为0 3 可满足式 不是矛盾式的就都是可满足式 重言式一定是可满足式 2 成真赋值与成假赋值 也叫成真指派与成假指派 一组原子的取值 真值指派
  • 矩阵树定理

    启蒙 http zhengruioi com contest 1416 T1 T2的10分暴力 后面是论文科技 不搞了 https www luogu com cn problem P6178 O n 3
  • 1600*D. Road Map(数学

    解析 记录每个点的父节点和子节点 从新的根节点开始遍历 遍历所有的非父结点即可 include
  • 最长公共上升子序列(LCIS)

    前置知识 LCS LIS 注意 刚开始看这个问题的时候 第一反应是先求出LCS再求出LCS的LIS 事实上这是有问题的 我们并不能保证这么求出的LCIS是最长的 比如下面这个例子 Example a 7 1 5 6 4 2 7 b 7 1
  • Summer Holiday HDU - 1827 强连通分量+缩点

    To see a World in a Grain of Sand And a Heaven in a Wild Flower Hold Infinity in the palm of your hand And Eternity in a
  • 714阿里巴巴模拟面试

    介绍一下数据库分页 https www nowcoder com questionTerminal 3577280c810546658f06f19c01ff0345 给定一棵树 求出这棵树的直径 即两个节点距离的最大值 应该是左右子树遍历深
  • 第14届蓝桥杯C++B组省赛

    文章目录 A 日期统计 B 01 串的熵 C 冶炼金属 D 飞机降落 E 接龙数列 F 岛屿个数 G 子串简写 H 整数删除 I 景区导游 J 砍树 今年比去年难好多 Update 2023 4 10 反转了 炼金二分没写错 可以AC了 U
  • 数模培训第二周——图论模型

    图论中最短路算法与程序实现 图论中的最短路问题 包括无向图和有向图 是一个基本且常见的问题 主要的算法有Dijkstra算法和Floyd算法 Floyd算法 简介 Floyd Warshall算法 英语 Floyd Warshall alg

随机推荐

  • 启用已签名的 kubelet 服务证书

    默认情况下 kubeadm 所部署的 kubelet 服务证书是自签名 Self Signed 这意味着从 metrics server 这类外部服务发起向 kubelet 的链接时无法使用 TLS 来完成保护 要在新的 kubeadm 集
  • Window查看apache的版本

    我使用的是xampp进入shell命令界面的 1 点击shell 进入 2 直接输入命令 httpd v 就可以看到你电脑平时使用的Apache版本了
  • 在线Qt查看源码网站

    Woboq Code Browser Explore C code on the web
  • Tomcat Manager 账号密码设置

    Windows版本 下载https tomcat apache org 我选择Tomcat9 可以查看包信息 详细信息介绍 所以Windows版本下载 我这里先下载了9 0 37 所以没有下载 解压以后 启动bin startup bat
  • 3-排序算法

    冒泡排序 冒泡排序的思路是将每两个数据之间进行大小比对 将大的数据后移 反复比对移动数据 直至数组排列整齐 include
  • Vosviewer+Pajek实现知网社交网络(解决中文乱码问题)

    Vosviewer Pajek实现知网社交网络 一 软件准备 Vosviewer安装 下载地址https www vosviewer com download 安装JDKhttps www oracle com java technolog
  • Python+Selenium_UI自动化操作(3)——刷新页面

    UI自动化 刷新页面 语法 refresh class TestRefreshWeb unittest TestCase def setUp self setUp是一个初始化方法 为test案例做数据准备 当前方法的数据准备动作是 启动ch
  • MQTT通讯协议简介和测试 [MQTT.fx]

    一 介绍 1 MQTT简介 MQTT Message Queuing Telemetry Transport 消息队列遥测传输 是IBM开发的一个即时通讯协议 有可能成为物联网的重要组成部分 该协议支持所有平台 几乎可以把所有联网物品和外部
  • 使用 POI创建一个简单的 Excel 文件

    初级程序员一枚 看到公司大佬写的生成Excel文件 做下记录 同时也分享给大家参考 实现原理是用workBook 然后还有OutputStream 首先是一个ExcelDataBean 这个实体类 用来 声明 Excel的最大行数 最大列数
  • 用tornado ,Supervisord ,nginx架网站

    最近使用 Tornado 重写了博客 于是查看了很多关于部署基于 Tornado 开发的网站的资料 比较成熟的方案就是使用 Nginx 来做反向代理 使用 Supervisord 来作为进程管理工具 至于什么叫反向代理 为什么 Tornad
  • 小鱼深度产品测评之:阿里云低代码开发平台魔笔,一站式的应用全生命周期管理,高效解决应用研发、迭代、运维的问题。

    低代码开发平台魔笔测评 1 引言 2 购买流程 3 魔笔 3 1添加应用 3 2 应用列表 3 1 1 列表应用展示 3 2 1 列应用操作 3 2 1 1 自动保存 3 2 1 2 复制功能 3 2 1 3 编辑功能 3 2 1 4 删除
  • (二)Android导航栏和菜单资源的结合使用

    ActionBar是Android3 0的重要更新之一 位于传统标题栏的位置 1 注意在使用ActionBar时保证该应用的目标版本应高于11 Android3 0的版本号
  • 知乎Python大佬带你10分钟入门Python爬虫(推荐收藏)

    一 基础入门 1 1 什么是爬虫 爬虫 spider 又网络爬虫 是指向网站 网络发起请求 获取资源后分析并提取有用数据的程序 从技术层面来说就是 通过程序模拟浏览器请求站点的行为 把站点返回的HTML代码 JSON数据 二进制数据 图片
  • 《软件调试的艺术》笔记--调试多线程程序

    下面是于线程相关的GDB命令用法汇总 info threads 给出关于当前所有线程的信息 thread 3 改成线程3 break 88 thread 3 当线程到达源代码88时停止执行 break 88 thread 3 if i 2
  • LaTeX教程(三)——文档格式排版

    文章目录 1 章节目录 1 1 生成章节 1 2 生成目录 2 交叉引用和脚注 2 1 交叉引用 2 2 脚注 3 特殊环境 3 1 列表 3 2 文本对齐 3 3 引用环境 3 4 代码环境 1 章节目录 1 1 生成章节 写文章或者论文
  • 关于程序员认知和编程学习,没有任何一篇文章会讲得如此透彻

    程序猿问科比 你为什么这么成功 科比 你知道洛杉矶凌晨四点是什么样子吗 程序猿 知道 一般那个时候我还在写代码 怎么了 科比 额 你以为程序员都是这样生活的 别瞎 他们只是在历劫 度过这段日子 他们就飞升上仙 乃至上神 过上另一种生活 就像
  • 【北京工业大学申请个人学生邮箱】

    北京工业大学申请个人学生邮箱 由于近期想要购买苹果设备 但是苹果更新了unidays认证 需要用到学生邮箱认证 因此花了几个小时查找北工大的邮箱注册方法 一趟下来 说实在的流程有些阴间 在此进行下记录 也方便后人的查找 一 进入 内网综合服
  • 商品上架、es应用到商品上架-35

    一 商品上架 上架的商品才可以在网站展示 上架的商品需要可以被检索 es是将数据保存到内存当中 所以我们不能将什么数据都保存到es当中 我们需要将重要的数据保存到es中 例如商品名称 规格型号 价格等信息 当需要的数据较多时 我们可以将主键
  • 我的博客地图

    最新更新时间 2020年03月12日11 37 13 猛戳 查看我的博客地图 总有你意想不到的惊喜 我的博客首页 我的博客创作中心 前端漫漫路 我的GitHub 我的站点 前言 由于自己写的文章越来越多 查看和查找具体内容变得格外耗时 因此
  • 【算法学习笔记】26:匈牙利算法(二分图最大匹配)

    1 简述 给定一个二分图 例如 匈牙利算法能够快速的计算出一种匹配方式 使得匹配的数量最多 注意 一个成功的匹配方式中 没有两条边是共用了同一个点的 形象的说 这个问题可以理解成二分图两边分别是男生和女生 有连线的表示可以凑成一对 匈牙利算