匈牙利算法:图论中寻找最大匹配的算法
首先讲一下数据结构中的图
1. 图就是一些顶点的集合,这些顶点通过一系列边结对(连接)注意:顶点有时也称为节点或者交点,边有时也称为链接。
图有各种形状和大小。边可以有权重(weight),即每一条边会被分配一个正数或者负数值。
树和链表都可以被当成是图,只不过是一种更简单的形式。他们都有顶点(节点)和边(连接)。
1)包含圈的图从一定点出发,经过一系列节点,最终返回最初的定点。
2)单向图回补到最终起点,比如说树。(DAG)
理论上,图就是一堆顶点和边对象而已.
有两种主要的方法:邻接列表和邻接矩阵。
邻接列表:在邻接列表实现中,每一个顶点会存储一个从它这里开始的边的列表。比如,如果顶点A 有一条边到B、C和D,那么A的列表中会有3条边。
查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。
邻接矩阵:在邻接矩阵实现中,由行和列都表示顶点,由两个顶点所决定的矩阵对应元素表示这里两个顶点是否相连、如果相连这个值表示的是相连边的权重
假设 V 表示图中顶点的个数,E 表示边的个数。
检查相邻性是指对于给定的顶点,尝试确定它是否是另一个顶点的邻居。在邻接列表中检查相邻性的时间复杂度是O(V),因为最坏的情况是一个顶点与每一个顶点都相连。
在 稀疏图的情况下,每一个顶点都只会和少数几个顶点相连,这种情况下相邻列表是最佳选择。如果这个图比较密集,每一个顶点都和大多数其他顶点相连,那么相邻矩阵更合适。
图中的边包含了3个属性:1。from 2。 图 3。权重
图的搜索算法:图的搜索指的就是从图的某一节点开始,通过边到达不同的节点,最终找到目标节点的过程。根据搜索的顺序不同,图的搜索算法可分为“广度优先搜索”和“深度优先搜索”两种。
图的最短路径问题:最短路径问题就是要在两个节点的所有路径中,找到一条所经过的边的权重总和最小的路径。相关算法有“贝尔曼-福特算法”,“狄克斯特拉算法”和“A* 算法”三种。
广度优先搜索:
广度优先搜索和深度优先搜索一样,都是从起点开始顺着边搜索,此时并不知道图的整体结构,直到找到指定节点(即终点)。在此过程中每走到一个节点,就会判断一次它是否为终点。
广度优先搜索会根据离起点的距离,按照从近到远的顺序对各节点进行搜索。而深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为止,然后再折返,开始搜索下一条路径。
在广度优先搜索中,有一个保存候补节点的队列,队列的性质就是先进先出,即先进入该队列的候补节点就先进行搜索。
下图中红色表示当前所在的节点(节点A),终点设为节点G,将与节点A直连的三个节点B, C, D放入存放候补节点的队列中(与节点A直连的三个节点放入时可以没有顺序,这里的放入顺序为B, C, D),用绿色表示。
此时队列中有B, C, D三个节点,我们来看看广度优先搜索是如何对各节点进行搜索的。
1、上面左图:此时从队列中选出一个节点,优先选择最早放入队列的那个节点,这里选择最左边的节点B。将已经搜索过的节点变为橙色(节点A),搜索到节点B时,将与节点B直连的两个节点E和F放入队列中,此时队列为 [C, D, E, F]。
2、上面中图:对节点B搜索完毕,节点B不是要找的终点,再搜索节点C,将与节点C直连的节点H放入队列中,此时队列为 [D, E, F, H]。
3、然后对节点D进行同样的操作,此时队列为 [E, F, H, I, J]。
4、上面右图:对与节点A直连的节点搜索完毕,再对与节点B直连的节点进行搜索(因为剩下的点中它们最先放入队列),这里选择节点E,将与节点E直连的节点K放入队列中,此时队列为 [F, H, I, J, K]。
然后一直按照这个规则进行搜索,直到到达目标节点G为止,搜索结束。
广度优先搜索为从起点开始,由近及远进行广泛的搜索。因此,目标节点离起点越近,搜索结束得就越快。
三、深度优先搜索
在深度优先搜索中,保存候补节点是栈,栈的性质就是先进后出,即最先进入该栈的候补节点就最后进行搜索。
还是将起点设为节点A,终点设为节点G,还是先将与节点A直连的三个节点B, C, D放入存放候补节点的栈中(这里的放入顺序为D, C, B)。到这里和广度优先搜索似乎没什么区别。
因为节点B是最后放入,则先从节点B开始搜索,将与节点B直连的两个节点E和F放入栈中,此时栈为 [D, C, F, E]。
1、上面左图:然后再对节点E进行搜索,将与节点E直连的节点K放入栈中,此时栈为 [D, C, F, K]。
2、此时节点K在栈的最后,所以先对节点K进行搜索,节点K不是终点,而且节点K没有其他直连的节点,所以此时栈为 [D, C, F]。
3、上面中图:然后再对节点F进行搜索,节点F也不是终点,而且节点F也没有其他直连的节点,所以此时栈为 [D, C]。
3、上面右图:接下来就对节点C进行搜索,将与节点C直连的节点H放入栈中,此时栈为 [D, H]。
然后一直按照这个规则进行搜索,直到到达目标节点G为止,搜索结束。
匈牙利算法用于解决二分图的最大匹配问题
增广路径是指,由一个未匹配的顶点开始,经过若干个匹配顶点,最后到达对面集合的一个未匹配顶点的路径,即这条路径将两个不同集合的两个未匹配顶点通过一系列匹配顶点相连。
将图 G 最大匹配初始化为空
while(从Xi点开始在图G中找到新的增广路径)
{
将增广路径假如到最大匹配中;
}
输出图 G 的最大匹配;
分图的匹配
顶点集 V 可分割为两个互不相交的子集,且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
当图中的顶点分为两个集合,使得第一个集合中的所有顶点都与第二个集合中的所有顶点相连时,此时是一特殊的二分图,称为完全二分图。
1.匹配
在给定一个二分图 G,在 G 的一个子图 M 中,若 M 的边集中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。匹配就是一个二分图中边的集合,其中任意两条边都没有公共顶点。下图,红边就是一个匹配,图中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。
2.最大匹配
给定二分图 G 中的所有匹配,所含匹配边数最多的匹配,称为这个图的最大匹配,选择最大匹配的问题即为图的最大匹配问题,下图,红边就是一个最大匹配
3.完全匹配
一个匹配中,图中每个顶点都与图中某条边相关联,则称此匹配为完全匹配,即一个图的某个匹配中,所有的顶点都是匹配点,就是一个完全匹配。完全匹配一定是最大匹配。但要注意的是,并非每个图都存在完全匹配。
简单来说,对于一个二分图,左点集中的每一个点都与右点集的一个点匹配,或者右点集中的每一个点都与左点集的一个点匹配。
4.完美匹配
对于一个二分图,左点集与右点集的点数相同,若存在一个匹配,包含左点集、右点集的所有顶点,则称为完美匹配。
简单来说,对于一个二分图,左点集中的每一个点都与右点集的一个点匹配,并且右点集中的每一个点都与左点集的一个点匹配。
如下图,红线所连接的匹配,不仅是一个完全匹配,还是一个完美匹配
5.最大匹配问题
举例来说,如下图所示,若存在某一对男孩和女孩之间存在相连的边,就意味着他们彼此喜欢。是否可能让所有男孩和女孩两两配对,使得每对儿都互相喜欢?这就是完全匹配问题。而最多有多少互相喜欢的男孩/女孩可以配对?这就是最大匹配问题。
6.最优匹配
带权二分图的权值最大的完全匹配称为最佳匹配,要注意的是,二分图的最优匹配不一定是二分图的最大权匹配。
实质上最优匹配问题就是求边权和最大的最大匹配问题。
求解技巧:可以添加一些权值为 0 的边,使得最优匹配和最大权匹配统一起来。
匈牙利算法常用于求最大匹配数、最小点覆盖数、最大独立集、最小路径覆盖数等;KM 算法常用于求最优匹配、边权和最小的完全匹配、最小有向环覆盖权值和、优先用原匹配边构建的最优匹配等。
二部图中的增广路径具有以下性质:
- 路径中边的条数是奇数;
- 路径的起点在二部图的左半边,终点在二部图的右半边;
- 路径上的点一个在左半边,一个在右半边,交替出现,整条路径上没有重复的点;
- 只有路径的起点和终点都是未覆盖的点,路径上其他的点都已经配对;
- 对路径上的边按照顺序编号,所有奇数编号的边都不在已知的匹配中,所有偶数编号的边都在已知的匹配中;
- 对增广路径进行“取反”操作,新的匹配数就比已知匹配数增加一个,也就是说,可以得到一个更大的匹配;
交替路:从一个未匹配的点出发,依次经过未匹配边、匹配边、未匹配边....这样的路叫做交替路。
增广路:从一个未匹配的点出发,走交替路,到达了一个未匹配过的点,这条路叫做增广路。
看下图,其中1、4、5、7是已经匹配的点,1->5,4->7是已经匹配的边,那么我们从8开始出发,8->4->7->1->5->2这条路就是一条增广路。
增广路径取反操作,就是把增广路径上奇数编号的边加入到已知匹配中,并把增广路径上偶数编号的边从已知匹配中删除。每做一次“取反”操作,得到的匹配就比原匹配多一个
算法轮廓:(1)置M(匹配)为空 (2)找出一条增广路径P,通过取反操作获得更大的匹配M’代替M (3)重复(2)操作直到找不出增广路径为止。
相当于:
初始时最大匹配为空
for 二分图A侧的每个点i
do 从点i出发寻找增广路径
如果找到,则把它取反(即增加了总了匹配数)。