hive 修改cluster by算法_学习记录 二分图匹配问题与匈牙利算法及KM算法

一、前言


在论文“Real-time Event Detection on Social Data Streams”中,作者首先在每一个时间窗口(分钟级)内利用社区发现算法(Louvain method)得到一个聚类  ,然后采用带权二分图最大匹配(maximum weighted bipartite matching)对  和上一时间窗口的聚类  进行聚类链接(Cluster Linking)。

We filter out any edges whose weight falls below a threshold and perform maximum weighted bipartite matching to find cluster links.

论文将节点之前的权重定义为:  与  各节点间共有的实体数目。

The edge weight between them is a measure of how many entities these clusters share, similar to the cosine similarity described earlier.

246895095d92272e2971f30d7adf5df0.gif

背景交代完,接下来我们就开始补充二分图匹配问题、匈牙利算法和KM算法的相关知识。

二、二分图匹配问题


所谓二分图(Bipartite Graph)就是这样一个图:

84b9dc65d7a4a981bb96978b96dc2166.png

简单地说,就是一张图里的所有点可以分为两组(如上图),并且每条边都跨越两组。这样的图就是二分图。

1. 二分图的定义

说的严谨一点:

二分图又称双分图、二部图、偶图,指顶点可以分成两个不相交的集U和V(U和V皆为独立集(Independent Sets)),使得在同一个集内的顶点不相邻(没有共同边)的图。

一个图为二分图仅当:

  • 没有奇数圈;

  • 点色数为2;

2. 相关的几个概念

7122c2d7-0c14-eb11-8da9-e4434bdf6706.png7222c2d7-0c14-eb11-8da9-e4434bdf6706.png65140bbb6b6794fdbc7515fdc880e3ad.png365ad9203fc9ee2c4bbf02ba66cc2ba4.png

我们定义匹配点、匹配边、未匹配点、非匹配边。如图3,1、4、5、7为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边、其他边为非匹配边。

  • 匹配(matching):二分图的一个“匹配”是指一些边的集合,任意两条边没有公共点。例如,图3、图4中红色的边就是图2的匹配。

  • 最大匹配(maximum matching):二分图的“最大匹配”,值的是二分图的所有匹配中边数最多的匹配。图4是一个最大匹配。它包含4条匹配边。

  • 完美匹配(perfect matching):二分图的一个“完美匹配”,是指所有点都在这个匹配中的一个匹配。也就是说这个匹配里的所有边刚好经过所有点一次。图4是一个完美匹配,显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。

举例来说:如下图所示,如果在某一对男孩和女孩之前存在相连的边,就意味着他们彼此喜欢。是否可能让所有男孩和女孩两两配对,使得每对都互相喜欢?图论中,这就是完美匹配问题。如果换一个说法:最多有多少对互相喜欢的男孩/女孩可以配对?这就是最大匹配问题。

7622c2d7-0c14-eb11-8da9-e4434bdf6706.jpeg

下面先讲匈牙利算法(Hungarian Algorithm),匈牙利算法用于求解无权二分图(unweighted bipartite graph)的最大匹配(maximum matching)和完美匹配(perfect matching)。

三、匈牙利算法


匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。美国数学家哈罗德·库恩于1955年提出该算法。此算法之所以被称作匈牙利算法,是因为算法很大一部分是基于以前匈牙利数学家 Dénes Kőnig 和 Jenő Egerváry 的工作之上创建起来的。詹姆士·芒克勒斯在 1957 年回顾了该算法,并发现(强)多项式时间的。此后该算法被称为 Kuhn–Munkres 算法或 Munkres 分配算法。原始算法的时间复杂度为 ,但 Edmonds 与卡普发现可以修改算法达到  运行时间,富泽也独立发现了这一点。Ford 和 Fulkerson 将该方法推广到了一般运输问题。2006 年发现卡尔·雅可比在 19 世纪就解决了指派问题,该解法在他死后在 1890 年以拉丁文发表。
——Wikipedia

这段文字为我们讲述匈牙利算法的历史姻缘……

1. 相关的几个概念和定理

在讲匈牙利算法之前,先学习几个概念,这些概念都是为匈牙利算法服务的。

eb5df0344776afdd9bcc4aee95841d52.png

交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。

增广路:从一个未匹配点出发,走交替路,如果以另一个未匹配点(出发的点不算)为结尾,则这条交替路称为增广路(agumenting path)。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):

986922628e233dbd1912e6ca363dd919.png

增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。

我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的。

增广路定理:任意一个非最大匹配的匹配一定存在增广路。

匈牙利树一般由BFS构造(类似于BFS树)。从一个未匹配点出发运行BFS(唯一的限制是,必须走交替路),直到不能再扩展为止。例如,由图7,可以得到如图8的一棵 BFS 树:

d84f8a15e7ef15f759b5e19462ee7b19.png5aeb779513833298e851c603182954ac.png

d77c8cd681861445f64e884da3ee0273.png

这棵树存在一个叶子节点为非匹配点(7 号),但是匈牙利树要求所有叶子节点均为匹配点,因此这不是一棵匈牙利树。如果原图中根本不含 7 号节点,那么从 2 号节点出发就会得到一棵匈牙利树。这种情况如图 9 所示(顺便说一句,图 8 中根节点 2 到非匹配叶子节点 7 显然是一条增广路,沿这条增广路扩充后将得到一个完美匹配)。

2. 算法基本原理

注意前面增广路的定义:“从一个未匹配点出发,走交替路,以另一个未匹配点为结尾”,首尾都是未匹配点,说明首尾的边都是非匹配边。而又是交替路,也就是说非匹配边比匹配边多一条。那么我们完全可以把这条增广路里的匹配边和非匹配边互换(称为“交换匹配”),那么匹配边就会多出 1 条,实现了“增广”的意义。并且这样做并不会对其他边造成影响,也不破坏二分图的性质。

那么我们就可以一直找增广路,不断交换匹配。根据增广路定理,如果找不到了,就说明已经达到最大匹配。

同样可以证明,已经匹配的点永远不会退出匹配,只会更换匹配。

这就是匈牙利算法最核心的部分了:一直找增广路,不断交换匹配。

可能看完上面的叙述,还是有点困惑。一直找增广路,不断交换匹配到底应该怎么做?以下我举一个便于理解例子:

7622c2d7-0c14-eb11-8da9-e4434bdf6706.jpeg

现在Boys和Girls分别是两个点集,里面的点分别是男生和女生,边表示他们之间存在“暧昧关系”。最大匹配问题相当于,假如你是红娘,可以撮合任何一对有暧昧关系的男女,那么你最多能成全多少对情侣?(数学表述:在二分图中最多能找到多少条没有公共端点的边)

现在我们来看看匈牙利算法是怎么运作的:

我们从B1看起(男女平等,从女生这边看起也是可以的),他与G2有暧昧,那我们就先暂时把他与G2连接(注意这时只是你作为一个红娘在纸上构想,你没有真正行动,此时的安排都是暂时的)。

8522c2d7-0c14-eb11-8da9-e4434bdf6706.jpeg

来看B2,B2也喜欢G2,这时G2已经“名花有主”了(虽然只是我们设想的),那怎么办呢?我们倒回去看G2目前被安排的男友,是B1,B1有没有别的选项呢?有,G4,G4还没有被安排,那我们就给B1安排上G4。

01d09563e15faf4dad0e4058aa6749d9.png

我们来细看这一过程:

  • 开始是B1——G2;

  • 由于B2的加入,有增广路G4——B1——G2——B2;

  • 然后交换匹配,成为G4——B1——G2——B2;

这是不是正是前面提到的一直找增广路,不断交换匹配。

我们继续,B3直接配上G1就好了,这没什么问题。至于B4,他只钟情于G4,G4目前配的是B1。B1除了G4还可以选G2,但是呢,如果B1选了G2,G2的原配B2就没得选了。我们绕了一大圈,发现B4只能注定单身了,可怜。(其实从来没被考虑过的G3更可怜)

583b3110fd439f87b6474a13a33524d9.png

匈牙利算法的要点如下:

1)从左边第1个顶点开始,挑选未匹配点进行搜索,寻找增广路。

  • 如果经过一个未匹配点,说明寻找成功。更新路径信息,匹配边数+1,停止搜索。

  • 如果一直没有找到增广路,则不再从这个点开始搜索。事实上,此时搜索后会形成一棵匈牙利树。我们可以永久地把它从图中删去,而不影响结果。

2)由于找到增广路之后需要沿着路径更新匹配,所以我们需要一个结构来记录路径上的点。DFS版本通过函数隐式地使用一个栈,而BFS版本使用一个队列。

3. 匈牙利算法的应用

这一部分其实与所要叙述的目标相关性不大,不过开阔开阔思路总是好的。

一些题目,乍一看与上面这个男女配对的问题没有任何相似点,其实都可以用匈牙利算法。例如:

(洛谷P1129) [ZJOI2007]矩阵游戏

题目描述 

小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏。矩阵游戏在一个    黑白方阵进行(如同国际象棋一般,只是颜色是随意的)。

每次可以对该矩阵进行两种操作: 

行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色) 

列交换操作:选择矩阵的任意两列,交换这两列(即交换对应格子的颜色) 

游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。 

对于某些关卡,小Q百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!于是小Q决定写一个程序来判断这些关卡是否有解。

输入格式 

第一行包含一个整数T,表示数据的组数。 

接下来包含T组数据,每组数据第一行为一个整数N,表示方阵的大小;

接下来N行为一个    的01矩阵(0表示白色,1表示黑色)。 

输出格式 

包含T行。对于每一组数据,如果该关卡有解,输出一行Yes;否则输出一行No。

我们把矩阵转化为二分图(左侧集合代表各行,右侧集合代表各列,某位置为1则该行和该列之间有边)。我们想进行一系列交换操作,使得X1连上Y1,X2连上Y2,……

大家可以想象,所谓的交换,是不是可以等价为重命名?我们可以在保持当前二分图结构不变的情况下,把右侧点的编号进行改变,这与交换的效果是一样的。

8a22c2d7-0c14-eb11-8da9-e4434bdf6706.jpeg

所以想让X1、X2...与Y1、Y2...一一对应,其实只需要原图最大匹配数为4就行了。(这与组合数学中相异代表系的概念相合)。实现代码及更多应用参见[1]

四、KM(Kuhn-Munkres)算法


好,前菜和头盘都上完了,现在我们来吃主菜,祝你有个好胃口!

1. 相关的几个概念和定理

完备匹配:定义 设G=为二部图,|V1|≤|V2|,M为G中一个最大匹配,且|M|=|V1|,则称M为V1到V2的完备匹配。也就是说把一个集合中的点全部匹配到另一个集合中。在上述定义中,若|V2|=|V1|,则完备匹配即为完美匹配,若|V1|最大匹配。

二分图最优匹配:对于二分图的每条边都有一个权(非负),要求一种完备匹配方案,使得所有匹配边的权和最大,记做最优完备匹配。(特殊的,当所有边的权为1时,就是最大完备匹配问题)

二分图带权匹配与最优匹配:什么是二分图的带权匹配?二分图的带权匹配就是求出一个匹配集合,使得集合中边的权值之和最大或最小,这个匹配集合比一定是完备匹配。而二分图的最优匹配则一定为完备匹配,在此基础上,才要求匹配的边权值之和最大或最小。二分图的带权匹配与最优匹配不等价,也不互相包含

顶标:每个节点与另一个集合中节点之间的最大权值

可行顶标(标杆):对于原图中的任意一个结点,给定一个函数  求出节点的顶标值。我们用数组  记录集合  中的节点顶标值,用数组  记录集合  中的节点顶标值。并且,对于原图中任意一条边  ,都满足  。

相等子图:设 G(V,E) 为二部图, G'(V,E') 为二部图的子图。如果对于 G' 中的任何边 满足,  ,我们称 G'(V,E') 为 G(V,E) 的等价子图或相等子图(是G的生成子图)。

以下是一些扩展的内容:

KM算法是求最大权完备匹配,如果要求最小权完备匹配怎么办?

方法很简单,只需将所有的边权值取其相反数,求最大权完备匹配,匹配的值再取相反数即可。

KM算法的运行要求是必须存在一个完备匹配,如果求一个最大权匹配(不一定完备)该如何办?

依然很简单,把不存在的边权值赋为0。

KM算法求得的最大权匹配是边权值和最大,如果我想要边权之积最大,又怎样转化?

还是不难办到,每条边权取自然对数,然后求最大和权匹配,求得的结果a再算出e^a就是最大积匹配。至于精度问题则没有更好的办法了。

2. 算法流程

这里有一篇比较好的男女找对象指南博客讲解了KM算法的执行过程,生动而又形象。主要理解过程中是怎么满足配对条件的,不满足后怎么办,过程中降低期望值d是怎么求得的,在配对失败期望值进行更改以后发生了那些变化,这些变化为新的一轮的匹配带来了什么?[2]

3. 算法原理

算法原理在[3]做了很好解释(好吧,是我写累了,还要去跑步),可以移步学习。

以上是KM算法的基本思想。但是朴素的实现方法,时间复杂度为  ——需要找  次增广路,每次增广最多需要修改  次顶标,每次修改顶标时由于要枚举边来求d值,复杂度为  。实际上KM算法的复杂度是可以做到  的。我们给每个Y顶点一个“松弛量”函数slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(x,y)时,如果它不在相等子图中,则让slack[y]变成原值与weight(x,y)-lx(x)-ly(y)的较小值(就是男生女生配对的求d的过程)。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的不在交错树中的Y顶点的slack值都减去d(对应就是未被访问的男生离女生更近了一步)。

以上就是全部内容,关于代码我会实现python版本的,在论文“Real-time Event Detection on Social Data Streams”论文笔记完成并复现完成后给出github地址,完结撒花。

引用:

[1] https://zhuanlan.zhihu.com/p/96229700

[2] https://www.cnblogs.com/wenruo/p/5264235.html

[3] https://blog.csdn.net/x_y_q_/article/details/51927054

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值