匈牙利算法-看这篇绝对就够了!

本文讲述的是匈牙利算法,即图论中寻找最大匹配的算法,暂不考虑加权的最大匹配(用KM算法实现),文章整体结构如下:

  1. 基础概念介绍
  2. 算法的实现

好的,开始!

一. 部分基础概念的介绍

我会严格介绍其定义,并同时用自己的大白话来重述。

概念点1. 图G的一个匹配是由一组没有公共端点的不是圈的边构成的集合。

这里,我们用一个图来表示下匹配的概念:

如图所示,其中的三条边即该图的一个匹配;所以,匹配的两个重点:1. 匹配是边的集合;2. 在该集合中,任意两条边不能有共同的顶点。

那么,我们自然而然就会有一个想法,一个图会有多少匹配?有没有最大的匹配(即边最多的匹配呢)?

我们顺着这个思路,继续往下走。

概念点2. 完美匹配:考虑部集为X={x1 ,x2, ...}和Y={y1, y2, ...}的二部图,一个完美匹配就是定义从X-Y的一个双射,依次为x1, x2, ... xn找到配对的顶点,最后能够得到 n!个完美匹配。

这里有一个概念,有点陌生,即什么是二部图,这个其实很好理解,给定两组顶点,但是组内的任意两个顶点间没有边相连,只有两个集合之间存在边,即组1内的点可以和组2内的点相连,这样构建出来的图就叫做二部图(更好理解就是n个男人,n个女人,在不考虑同性恋的情况下,组成配偶)。这样是不是简单多了?

既然说到了双双组成配偶,那我们干的就是月老做的活了,古话说得好,宁拆一座庙,不毁一桩婚,如果真的给出n个帅气的男孩,n个漂亮的女孩,他们之间互相有好感,但一个男孩可以对多个女孩有感觉,一个女孩也可能觉得多个男孩看起来都不错,在这种情况下,我们怎么让他们都能成双成对呢?

将这个问题抽象出来,互有好感就是一条条无向边(单相思我们先不考虑),而男孩和女孩就是一个个节点,我们构建出这么一个图,而完美匹配就是让所有看对眼的男孩和女孩都能够在一起。

完美匹配是最好的情况,也是我们想要的情况。

当然,有些情况下我们做不到完美匹配,只能尽可能实现最多的配对,这个就叫做最大匹配。

可以看出来,完美匹配一定是最大匹配,而最大匹配不一定是完美匹配。

那么,作为月老的我们,核心目标就是找到最大匹配了。

在我们思考如何完成这个艰巨的任务之前,我们引入几个可能不太好理解的概念。

3.交错路径:给定图G的一个匹配M,如果一条路径的边交替出现在M中和不出现在M中,我们称之为一条M-交错路径

而如果一条M-交错路径,它的两个端点都不与M中的边关联,我们称这条路径叫做M-增广路径

举个例子:

在上图中,有五条边,按照匹配的概念,2, 4两条加粗的边是一个匹配,目光锐利的你或许同时发现了,1, 3, 5是不是也是一个匹配呢?

毫无疑问,是的。

套用我们说的M-交错路径的概念,如果我们从2, 4 所构成的匹配M出发,会发现 1, 2, 3, 4, 5 这条路径是M的一条交错路径,同时它还满足两个端点都不与M中的边所关联。

是不是发现个奇怪的地方呢?我们完全可以从1, 2, 3, 4, 5 这条路径中找到一个更大的匹配,而这个匹配比原先的匹配M多一条边,是一个比原先M更大的匹配!

所以,我们寻找最大匹配的任务就相当于我们不断地在已经确定的匹配下,不断找到新的增广路径,因为出现一条增广路径,就意味着目前的匹配中增加一条边嘛!

看起来复杂的问题,变成了寻找增广路径这么个解决问题的想法了。

当图中再没有增广路径了,就意味着我们找到了该图的最大匹配了。

说明下:我们这里所讨论的匹配,是图论中的任务分配问题,通常是针对于二部图发起的,想想也是,匹配不就是配对么,自然是两两成对了。

好,基础概念介绍完了,我们接下来给个例子,探讨我们的匈牙利算法,它就是通过不断寻找增广路径的办法,打开了通向最大匹配的道路。

二. 匈牙利算法

下面我们讨论下匈牙利算法的内容:

1. 给定一个图:

前面已经说了,我们讨论的基础是二部图,而上图就是一个二部图,我们从上图的左边开始讨论,我们的目标是尽可能给x中最多的点找到配对。

注意,最大匹配是互相的,如果我们给X找到了最多的Y中的对应点,同样,Y中也不可能有更多的点得到匹配了。

刚开始,一个匹配都没有,我们随意选取一条边,(x1, y1)这条边,构建最初的匹配出来,结果如下,已经配对的边用粗线标出:

2. 我们给x2添加一个匹配,如下图的(x2, y2)边。

目前来看,一切都很顺利,到这里,我们形成了匹配M,其有(x1,  y1), (x2,  y2 ) 两条边。

3. 我们现在想给x3匹配一条边,发现它的另一端y1已经被x1占用了,那x3就不高兴了,它就去找y1游说,让y1离开x1。

即将被迫分手的x1很委屈,好在它还有其他的选择,于是 x1 妥协了,准备去找自己看中的y2

但很快,x1发现 y2 被x2 看中了,它就想啊,y1 抛弃了我,那我就让 y2 主动离开 x2 (很明显,这是个递归的过程)

x2 该怎么办呢?好在天无绝人之路,它去找了y5

谢天谢地,y5 还没有名花有主,终于皆大欢喜。

匹配如下:

上面这个争论与妥协的过程中,我们把牵涉到的节点都拿出来:(x3, y1, x1, y2, x2, y5),很明显,这是一条路径P。

而在第二步中,我们已经形成了匹配M,而P呢?还记得增广路径么,我们发现,P原来是M的一条增广路径!

上文已经说过,发现一条增广路径,就意味着一个更大匹配的出现,于是,我们将M中的配对点拆分开,重新组合,得到了一个更大匹配,M1, 其拥有(x3,  y1),(x1,  y2),  (x2,  y5)三条边。

而这,就是匈牙利算法的精髓。

同样,x4 , x5 按顺序加入进来,最终会得到本图的最大匹配。

得到这个结果后,我们发现,其实也可以把y4 让给 x6 , 这样x5 就会空置,但并不影响最大匹配的大小。

总结:

1. 匈牙利算法寻找最大匹配,就是通过不断寻找原有匹配M的增广路径,因为找到一条M匹配的增广路径,就意味着一个更大的匹配M' , 其恰好比M 多一条边。

2. 对于图来说,最大匹配不是唯一的,但是最大匹配的大小是唯一的。

  • 382
    点赞
  • 1011
    收藏
    觉得还不错? 一键收藏
  • 55
    评论
匈牙利算法,也称为增广路径算法,用于求解二分图的最大匹配问题。下面是匈牙利算法的流程以及Python程序实现。 1. 定义一个二分图,其中左边的节点集合为X,右边的节点集合为Y。 2. 初始化一个与Y节点集合等大的数组match,并将所有元素初始化为-1,用于记录节点的匹配关系。同时定义一个数组vis,用于标记X中的节点是否被访问过。 3. 对于X中的每一个节点u,依次进行以下操作: a. 初始化vis数组,将其所有元素置为False。 b. 递归查找u的增广路径,如果找到了一条增广路径,则将匹配关系进行更新。 4. 定义递归函数findPath(u),用于查找从节点u开始的增广路径: a. 遍历Y中的每一个节点v,如果节点u和节点v之间存在边,并且节点v未被访问过,则进行以下操作: i. 将节点v标记为已访问。 ii. 如果节点v未被匹配,或者节点v已被匹配但是从match[v]出发可以找到增广路径,则将节点u和节点v之间建立匹配关系,然后返回True。 iii. 如果节点v已被匹配,并且从match[v]出发找不到增广路径,则继续查找其他节点。 b. 如果遍历完所有的节点后仍未找到增广路径,则返回False。 5. 输出匹配结果。 下面是Python程序实现: ```python def findPath(u): for v in range(len(Y)): if graph[u][v] and not vis[v]: vis[v] = True if match[v] == -1 or findPath(match[v]): match[v] = u return True return False def hungarian(): global match match = [-1] * len(Y) cnt = 0 for u in range(len(X)): global vis vis = [False] * len(Y) if findPath(u): cnt += 1 return cnt # 二分图的左边节点集合 X = [] # 二分图的右边节点集合 Y = [] # 初始化二分图的邻接矩阵 graph = [[0] * len(Y) for _ in range(len(X))] # 添加边到邻接矩阵 # 调用匈牙利算法求解最大匹配数量 max_matching = hungarian() # 输出匹配数量 print(max_matching) ``` 请注意,上述的程序实现中,X和Y分别表示二分图的左边和右边的节点集合,graph为二分图的邻接矩阵表示方式。在实际使用时,需要根据具体问题进行相应的修改和补充。
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值