Kuhn-Munkres 算法详细解析

2023-05-16

直接进入正题,Kuhn-Munkres 算法(下文简称 KM 算法)是为了高效求解二分图最佳完美匹配问题而生的,我们先温习一下几个概念,如果你对这几个概念不是很熟悉的话,建议先去学习。

概念

  • 匹配(匹配边): 在图 GG 中两两没有公共端点的边集合 MM
  • 二分图最大匹配:给出一个二分图,找一个边数最大的匹配。即选尽可能多的边,使得任意选中的两条边均没有公共顶点。

不要被概念弄的晕了,用最直观的方式考虑。情景是有一个班级的学生要结成男女两两一组,但每个学生只想自己喜欢的异性结成一组,于是这就会有冲突。由于男男、女女不会结成一组,这是一个二分图。二分图最大匹配就是要给出一个最优方案,使得结成的组数最多。

下面说复习二分图最大匹配相关:
为了方便叙述,我们将二分图染色后同颜色的节点放在同一侧,形成左侧(XX 集),右侧(YY 集)。

  • 交替路:从任意一个未匹配点出发,依次经过未匹配边-匹配边-非匹配边-匹配边-未匹配边……所得到的路径被称为交替路。
  • 增广路:如果一条交替路的终点是一个未匹配点,那么这条路径是增广路,由于从未匹配点出发,又从未匹配点结束,未匹配边比未配边多一条。
  • 增广路定理:如果可以找到一条增广路,那么将匹配边与未匹配边互换,这个匹配就可以多一条边,否则当前匹配就是最大匹配。即任意一个匹配是最大匹配的充分必要条件是不存在增广路。

增广路互换的实质可以这么考虑,如上图:从未匹配点 A 出发,A 想与 B 匹配,于是通过未匹配边找到 B,然而 B 已经是匹配点,于是只能经过匹配边去问 C 能不能与别人匹配,C 经过未匹配边找到 D,由于 D 是未匹配点,所以 C 成功与 D 匹配。CD 之间的边变为匹配边;BC 之间解除关系,变为未匹配边;AB 之间建立关系,变为匹配边。这便是增广路互换的实质。

  • 二分图完美匹配:如果一个二分图的所有点都是匹配点(匹配边中某一条边的端点),则称这个匹配是完美匹配。

回到上面的情景,完美匹配就是可以得到一个方案,使得所有男女同学都可以结成两两一组。

  • 二分图最大完美匹配:假定有一个二分图 GG,每条边有一个权值(可为负数),权值和最大的完美匹配是二分图最大完美匹配。

算法

KM 算法是通过给每个顶点一个标号(叫做顶标)来把求最大权完美匹配的问题转化为求完美匹配的问题的。可以简单理解为节点函数就是节点的一个值。
可行顶标就是对于所有顶点的函数值 ll,使得对于任意弧 e(x \rightarrow y)e(xy),都满足 l_{x} + l_{y} \ge W_{e}lx+lyWeKM 算法的顶标自始至终满足这一条件。
接着是相等子图,相等子图包含原图中所有的点,但只包含满足 l_{x} + l_{y} = W_{e}lx+ly=We 的所有弧 e(x \rightarrow y)e(xy),根据定义,这些弧一定是当前最大的弧(不等式已经取到等号),那么如果相等子图有完美匹配,那这个完美匹配一定是最大完美匹配。因为相等子图的权值和为所有点的顶标之和,而随便一个匹配中的边因为受到 W_{e} \le l_{x} + l_{y}Welx+ly 的限制,不可能比所有点的顶标之和大,所以这个极为重要的定理还是很好证明的。
所以算法的主要矛盾就在于寻找可行顶标,使得相等子图有完美匹配。可行顶标的修改过程中,每一步都运用了贪心的思想,这样我们的最终结果一定是最优的。下面是算法的叙述:
因为有 l_{x} + l_{y} = W_{e}lx+ly=We 恒成立,我们设左侧(YY 集)的所有节点顶标为 00,那么所有 XX 集的点的顶标就必须为从它出发所有的边的最大值。
接着求其完美匹配,如果成功,结束算法,否则必须修改顶标,使得有更多的边能够参与进来。
我们求当前相等子图的完美匹配失败,是因为对于某个未匹配顶点 uu,我们找不到一条从它出发的增广路,这时我们只能获得一条交替路。我们把 XX 集中在交替路的点集叫做 SS, XX 集中不在交替路的点集叫做 S'S,同理 YY 集中在交替路的点集叫做 TT, YY 集中不在交替路的点集叫做 T'T
如果我们把交替路中 XX 集顶点的顶标全都减小某个值 ddYY 集的顶标全都增加同一个值 dd,那么我们会发现:

  • 两端都在交替路中的边 e(i \rightarrow j)e(ij)l_{i} + l_{j}li+lj 的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
  • 两端都不在交替路中的边 e(i \rightarrow j)e(ij)l_{i}, l_{j}li,lj 都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
  • XX 集一端在 S'S 中, YY 端在 TT 中的边 e(i \rightarrow j)e(ij),它的 l_{i}, l_{j}li,lj 的值有所增大。它原来不属于相等子图,现在仍不可能属于相等子图。
  • XX 集一端在 SS 中,YY 端在 T'T 中的边 e(i \rightarrow j)e(ij),它的 l_{i}, l_{j}li,lj 的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。


也就是说,只有 XX 集一端在 SS 中,YY 端在 T'T 中的边才有可能被选中。继续贪心,我们只能让满足条件的边权最大的边被选中,即满足 l_{x} + l_{y} = W_{e}lx+ly=We,那么这个 dd 值,就应该取 d = \min\{l_{x} + l_{y} - W_{e(x\rightarrow y)}\ \vert \ x \in S, y \in T'\}d=min{lx+lyWe(xy)  xS,yT}
于是有新的边加入相等子图,我们可以愉快的继续对于未匹配顶点 uu 寻找增广路,这样的修改最多进行 nn 次,而一共有 nn 个点,所以除去修改顶标的时间,复杂度已经达到 O(n^{2})O(n2)。因此算法的复杂度主要取决于修改顶标的时间。
思路一:枚举所有 n^{2}n2 条边,看是否满足条件,满足条件就更新 dd 值。最直观清晰,然而总的复杂度飙升至 O(n^{4})O(n4)
思路二:对于 T'T 的每个点 vv,定义松弛变量 slack(v) = \min\{l_{x}+l_{y} -W_{e(x\rightarrow y)}\ \vert\ x\in S\}slack(v)=min{lx+lyWe(xy)  xS},这个松弛变量在匹配的过程中就可以更新,修改顶标的过程中 d = \min\{slack(v)\ \vert\ v \in T'\}d=min{slack(v)  vT}。总复杂度 O(n^{3})O(n3),但不是严格的(想一想为什么)?但实际已经够用。
KM 算法仅仅只适用于找二分图最佳完美匹配,如果无完美匹配,那么算法很可能陷入死循环(如果不存在的边为 -INF 的话就不会,但正确性就无法保证了),对于这种情况要小心处理。
最后回顾一下总的流程,理一下思路:

  1. 初始化可行顶标。
  2. 用增广路定理寻对每个点找匹配。
  3. 若点未找到匹配则修改可行顶标的值。
  4. 重复2、3步直到所有点均有匹配为止,即找到相等子图的完美匹配为止。

模版


const int maxn = 500 + 3, INF = 0x3f3f3f3f;
int n, W[maxn][maxn];
int mat[maxn];
int Lx[maxn], Ly[maxn], slack[maxn];
bool S[maxn], T[maxn];

inline void tension(int &a, const int b) {
    if(b < a) a = b;
}

inline bool match(int u) {
    S[u] = true;
    for(int v = 0; v < n; ++v) {
        if(T[v]) continue;
        int t = Lx[u] + Ly[v] - W[u][v];
        if(!t) {
            T[v] = true;
            if(mat[v] == -1 || match(mat[v])) {
                mat[v] = u;
                return true;
            }
        }else tension(slack[v], t);
    }
    return false;
}

inline void update() {
    int d = INF;
    for(int i = 0; i < n; ++i)
        if(!T[i]) tension(d, slack[i]);
    for(int i = 0; i < n; ++i) {
        if(S[i]) Lx[i] -= d;
        if(T[i]) Ly[i] += d;
    }
}

inline void KM() {
    for(int i = 0; i < n; ++i) {
        Lx[i] = Ly[i] = 0; mat[i] = -1;
        for(int j = 0; j < n; ++j) Lx[i] = max(Lx[i], W[i][j]);
    }
    for(int i = 0; i < n; ++i) {
        fill(slack, slack + n, INF);
        while(true) {
            for(int j = 0; j < n; ++j) S[j] = T[j] = false;
            if(match(i)) break;
            else update();
        }
    }
}

  

参考目录:http://www.nocow.cn/index.php/Kuhn-Munkres%E7%AE%97%E6%B3%95 & 刘汝佳:《训练指南》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kuhn-Munkres 算法详细解析 的相关文章

  • 关于函数strtok和strtok_r的使用要点和实现原理(一)

    strtok函数的使用是一个老生常谈的问题了 该函数的作用很大 xff0c 争议也很大 以下的表述可能与一些资料有区别或者说与你原来的认识有差异 xff0c 因此 xff0c 我尽量以实验为证 交代一下实验环境是必要的 xff0c winx
  • vncserver too many security failures

    在服务器上开了几个虚拟机 xff0c 装了VNC之后 xff0c 经常遇到报错too many security failures 查了下相关资料 xff0c 原来是有人在暴力破解 xff0c 触发了VNC的黑名单机制 重置黑名单 xff0
  • Centos7 官方安装方法(略有修改),nextcloud-20.0.1版本亲测

    Centos7 官方安装方法 略有修改 nextcloud 20 0 1版本亲测 执行第一个脚本文件 文件内容如下 bin bash systemctl stop firewalld systemctl disable firewalld
  • SecureCRT_Python笔记

    文章目录 说明常用语法python脚本开头格式Sleep睡眠发送命令或文本弹出消息框 常用对象操作Application 应用程序对象属性ActivePrinter 活动打印机ScriptFullName 脚本名称Version 版本 方法
  • 常用Docker项目合集

    文章目录 使用说明docker官方一键安装脚本使普通用户可以使用Docker使用国内加速器Portainer 容器管理Portainer 官方 常用服务器备份同步使用docker部署backuppc文件备份adferrand backupp
  • Python习题(第2课)

    一 天天向上的力量 C 一年365天 xff0c 以第1天的能力值为基数 xff0c 记为1 0 当好好学习时 xff0c 能力值相比前一天提高N xff1b 当没有学习时 xff0c 由于遗忘等原因能力值相比前一天下降N 每天努力或放任
  • Openpyxl笔记

    文章目录 介绍工作薄操作创建工作薄对象打开工作薄工作薄属性工作薄方法遍历工作薄中的工作表 工作表操作创建工作表指定工作表删除工作表复制工作表获取工作表的总行数和总列数工作表的Table创建Table删除Table 工作表属性工作表方法获取工
  • xlwings笔记

    文章目录 介绍xlwings 常用实例读取 写入单元格将列表写入某个范围 xlwings 安装顶级函数顶级函数xlwings load顶级函数xlwings view appsapps 介绍apps属性查看Apps包含的所有App返回App
  • NextCloud 最新官方源代码安装包及客户端下载

    官方搬运 服务端 源代码安装包 大版本小版本V13V13 0 5下载V14V14 0 1下载V15V15 0 4下载V15V15 0 5下载V15V15 0 7下载V16V16 0 0下载V16V16 0 1下载V16V16 0 2下载V1
  • 河北保定电信家庭宽带获取原生IPv6地址,中兴F650光猫加MikroTik路由器

    先用电脑接光猫输入192 168 1 1 用户名 xff1a telecomadmin 密码 xff1a nE7jA 5mROS路由器设置
  • [翻译]Learning Deep Features for Discriminative Localization

    英文原文请点这里 摘要 在这项工作中 xff0c 我们重新审视了 Network in network 中提出的全局平均 池化层 xff08 global average pooling xff09 xff0c 并阐明了它是如何通过图片标签
  • 2. 嵌入式OpenWRT入门基础篇-----OpenWRT固件烧录

    OpenWRT固件烧录方式有很多种 xff0c 会逐渐更新烧录方法 一 此方法适用于OpenWRT 系统可以正常启动的情况 xff0c 用于OpenWRT 固件的升级 1 xff09 开发板上电 xff0c 至启动完成 2 xff09 登录
  • OpenCV源码中Haar训练及提取特征的代码

    我想计算Haar特征 xff0c 自己手动计算感觉挺麻烦 xff08 主要在取各个不同位置 不同scale的特征 xff09 xff0c 而且可能速度不够 OpenCV 的这个把所有东西都封装起来了 xff0c 由于我的online boo
  • 梯度向量、Jacobian矩阵、Hessian矩阵

    这里 xff0c 讨论三个概念 xff1a 梯度向量 Jacobian矩阵 Hessian矩阵 xff1b 由自变量x 61 x1 x2 xn T 因变量 xff1a 为一维f x 时 xff0c 此时其一阶导数构成的向量为梯度向量g x
  • linux查看当前加载的所有动态库

    在我们做Linux开发的时候 xff0c 往往会出现 某些库 can not found 的情况 xff0c 在我们添加了这些库之后 xff0c 如何查看这些库的路径是否被识别了呢 xff1f 下面介绍一个命令 xff1a ldconfig
  • 《软件工程》实训报告

    摘要 软件工程是一门研究用工程化方法构建和维护有效的 实用的和高质量的软件的学科 它涉及程序设计语言 数据库 软件开发工具 系统平台 标准 设计模式等方面 该论文主要回顾了本人在迪丽瑟斯网站开发中的实践经历和项目经验 xff0c 以及得出的
  • ERROR 1129 (00000): #HY000Host ‘*.*.*.*’ is blocked because of many connection errors;

    今天使用工具nvicat连接mysql的时候报错误 xff1a ERROR 1129 00000 HY000Host is blocked because of many connection errors unblock with mys
  • Win7使用附件中的远程桌面连接Ubuntu 15.04图形界面(xrdp方法)

    Ubuntu15 04下 以下命令行皆是在终端中运行 xff1a 安装xrdp span class hljs built in sudo span apt get install xrdp 安装vnc4server span class
  • Qt设置常见窗口背景色几种方式

    常见窗口背景色 总结qt 常见设置QWidget 类型窗口背景色几种方式 setStyleSheet ui span class token punctuation span widget span class token operator
  • 操作手册(GB8567——88)

    操作手册 xff08 GB8567 88 xff09 1 引言 1 1 编写目的 说明编写这份操作手册的目的 xff0c 指出预期的读者 1 2 前景 说明 xff1a a xff0e 这份操作手册所描述的软件系统的名称 xff1b b x

随机推荐