Bipartite Matching and Hungarian Algorithm.
1. 二分图的基本概念
设是一个无向图,如果顶点集合可分割为两个互不相交的子集,并且边集合中每条边关联的两个顶点分别属于这两个子集,则称图为一个二分图(bipartite graph)。
下图给出了一个二分图的例子,其顶点集合,边集合
2. 图的匹配
对于二分图的一个子图,如果子图的边集合中的任意两条边都不依附于同一个顶点,则称是一个匹配(matching)。匹配建立了二分图的两个子集中部分顶点的一一对应关系。
下图给出了二分图的两个匹配和:
匹配的边集合所关联的点称为饱和点,其余点称为非饱和点。如上图中:
- 的饱和点:
- 的饱和点:
在图的所有匹配中,所含匹配边数最多的匹配称为图的最大匹配。 如果图的某个匹配使得图的所有顶点都是饱和点,则该匹配是一个完美匹配。完美匹配一定是最大匹配,但并非每个图都存在完美匹配。
定义图中的一条路径,如果该路径中的边在属于匹配和不属于匹配中交替出现,则称其为交错路。下图分别给出了匹配和的一条交错路:
对于匹配的一条交错路,如果该交错路的起点和终点都是匹配的非饱和点,则该交错路为增广路。下图给出了匹配的一条增广路。
增广路具有以下结论:
- 增广路的路径边数为奇数,且第一条边和最后一条边都不属于匹配;
- 增广路中匹配边的数量总是比非匹配边的数量多条;
- 将增广路中边的匹配方式取反,会得到一个更大的匹配,匹配数;
- 匹配是图的最大匹配等价于不存在的增广路。
3. 二分匹配与匈牙利算法
二分匹配(Bipartite Matching)问题即寻找二分图中的最大匹配。
匈牙利算法(Hungarian Algorithm)最早是由匈牙利数学家D.Kőnig用来求矩阵中元素个数的一种方法,由此他证明了“矩阵中独立元素的最多个数等于能覆盖所有元素的最少直线数”。
1955年W.W.Kuhn在求解指派问题时引用了这一结论, 并提出根据二分图的增广路寻找最大匹配的算法,仍然称为“匈牙利算法”。
指派问题是人员调度问题中的经典问题:由个人完成项工作,第个人完成第项工作的成本为,由此构造成本矩阵。现需确定指派方案使得完成任务的总成本最低。特别地,若工作成本,则成本矩阵可以padding为图的连接矩阵,这等价于图论中的最大匹配问题。
匈牙利算法的思路为:
- 设二分图的初始匹配为空;
- 寻找一条增广路,通过取反操作构造一个更大的匹配代替匹配;
- 重复上述步骤直至寻找不到增广路。
在实现时可以通过深度优先搜索进行增广路径的选择。
- 首先从任意的一个未配对的点开始,任意选一条边开始配对。
- 如果点未配对过,则配对成功,对应一条增广路。
- 如果点已经被配对,则尝试调整与点配对的点,使其与其余未配对的点进行配对,使得点能与点成功配对,并更新原来的配对关系。
- 如果点始终未能与点成功配对,则从点的边中重新选一条边,直到点配对成功或尝试过点的所有边为止。
- 然后对剩下的未配对点进行配对,直到遍历所有点,找不到新的增广路为止。
下面给出匈牙利算法的简单实现:
def Hungarian(G):
N, M = G.shape # G[u,v]=1表示结点u,v之间有连接
matched = [None] * N # 记录已匹配的结点
def found(x): # 通过DFS寻找增广路
for m in range(M):
if G[x, m] != 1 or visited[m]:
continue
visited[m] = 1
if not matched[m] or found(matched[m]):
matched[m] = x
return True
return False
for n in range(N):
visited = [0] * N # 记录已访问的结点
found(n)
return [*enumerate(matched)] # 返回(v,u)
使用scipy库可以方便地实现匈牙利算法:
from scipy.optimize import linear_sum_assignment
match_index_list = linear_sum_assignment(cost_matrix)
# ([X1, X2, ...], [Y1, Y2, ...])
Related Issues not found
Please contact @0809zheng to initialize the comment