匹配问题与匈牙利算法

匹配问题

M M M为图 G = ( V , E ) G=(V, E) G=(V,E)的边集合的子集,即 M ⊆ E M \subseteq E ME
如果 M M M中的任意两条边都没有公共顶点,我们则称 M M M G G G的一个匹配

实际意义

匹配问题主要应用于二部图。对于二部图 G = ( U , V , E ) G=(U, V, E) G=(U,V,E),比如说用U中的顶点代表员工,用V中的顶点代表工作。当员工 u u u可以担任 v v v工作时,有 ( u , v ) ∈ E (u, v) \in E (u,v)E。假设每个人不能承担两份工作,而每份工作只需要一个人来做。在这种情况下,图 G G G中的匹配就对应了在满足上述条件下给员工分配工作的方案。

最大匹配

如果某顶点于 M M M中的边相关联,我们说该顶点被匹配(matched)。
M M M中的边的条数称为 M M M的大小,用 ∣ M ∣ |M| M表示。
如果所有顶点都被匹配到了,我们则称该匹配为完美匹配

对于 G = ( V , E ) G=(V, E) G=(V,E)的匹配 M M M,如果向 M M M中添加 E − M 中 E-M中 EM的任意一条边都可以使其变为非匹配,我们则成 M M M极大匹配
如果 M M M G G G所有匹配中边数最大的,则称 M M M最大匹配
最大匹配一定是极大匹配,但极大匹配不一定是最大匹配。

二部图中的完美匹配

对于满足 ∣ U ∣ = ∣ V ∣ |U| = |V| U=V的二部图 G = ( U , V , E ) G=(U, V, E) G=(U,V,E)中的匹配,可以看出以下命题成立

∃ S ⊆ U , ∣ δ ( S ) ∣ < ∣ S ∣ ⟹ G 中不存在完美匹配 \exists S \subseteq U,|\delta(S)| < |S| \Longrightarrow G中不存在完美匹配 SUδ(S)<SG中不存在完美匹配
其中 δ ( S ) \delta(S) δ(S)表示与S内的至少一个顶点相邻的顶点的集合

进一步地,有霍尔定理

G 中存在完美匹配 ⟺ ∀ S ⊆ U , ∣ δ ( S ) ∣ ≧ ∣ S ∣ G中存在完美匹配 \Longleftrightarrow \forall S \subseteq U,|\delta(S)| \geqq |S| G中存在完美匹配SUδ(S)S

其中条件

∀ S ⊆ U , ∣ δ ( S ) ∣ ≧ ∣ S ∣ \forall S \subseteq U,|\delta(S)| \geqq |S| SUδ(S)S

可以称为霍尔条件

简要证明如下

存在完美匹配的图G显然满足霍尔条件,下面用数学归纳法证明,满足霍尔条件的图G存在完美匹配。
证明思路是对 ∣ U ∣ |U| U使用数学归纳法。对于 ∣ U ∣ = 1 |U|=1 U=1的图,显然。
假设对 ∣ U ∣ ≦ k |U| \leqq k Uk的所有图,命题成立。下面证明对于 ∣ U ∣ = k + 1 |U| = k + 1 U=k+1的任意图G,命题也成立。
分两种情况
情况一 对于任意的非空的 S ⊂ U ,都有 ∣ δ ( S ) ∣ ≧ ∣ S ∣ + 1 对于任意的非空的S \subset U,都有|\delta(S)| \geqq |S|+1 对于任意的非空的SU,都有δ(S)S+1
对于这种情况,可以直接任意取一条边,比如 ( 1 , a ) (1, a) (1,a),将其删除,同时删除顶点 1 , a 1, a 1,a以及关联的所有边。
将得到的图记为 G ′ = ( U ′ , V ′ , E ′ ) G'=(U', V', E') G=(U,V,E) ∣ U ′ ∣ = k |U'|=k U=k,且 G ′ G' G满足霍尔条件。
由归纳假设知, G ′ G' G中存在完美匹配 M ′ M' M M ′ ∪ { ( 1 , a ) } M' \cup \{(1, a)\} M{(1,a)}正是G的完美匹配。
情况二 存在非空的 S ⊂ U , δ ( S ) ∣ = ∣ S ∣ 存在非空的S \subset U,\delta(S)|=|S| 存在非空的SUδ(S)=S
首先,我们来思考由 S S S δ ( S ) \delta(S) δ(S)导出的子图 G 1 G_1 G1。因为 G G G满足霍尔条件,所以 G 1 G_1 G1同样满足霍尔条件。
又由于 S S S U U U的真子集,所以 ∣ S ∣ ≦ k |S| \leqq k Sk。根据归纳假设,图 G 1 G_1 G1中有完美匹配 M 1 M_1 M1
接下来,从 G G G中删除所有 S ∪ δ ( S ) S \cup \delta(S) Sδ(S)中的顶点,同时删除于其关联的所有边。
将新图记为 G 2 = ( U 2 , V 2 , E 2 ) G_2=(U_2, V_2, E_2) G2=(U2,V2,E2)
下面用反证法证明新图 G 2 G_2 G2满足霍尔条件。
反之,若图 G 2 G_2 G2不满足霍尔条件,即 存在 S ′ ⊂ U 2 使得 ∣ δ G 2 ( S ′ ) ∣ < ∣ S ∣ 成立 存在S' \subset U_2使得|\delta_{G_2}(S')| < |S|成立 存在SU2使得δG2(S)<S成立.
这里的 S ′ S' S的相邻顶点不再以图 G G G中的邻接关系为基础,而是以图 G 2 G_2 G2中的邻接关系为基础。
在原来的图 G G G中, δ ( S ) \delta(S) δ(S) δ G 2 ( S ) \delta_{G_2}(S) δG2(S)不相交,有 ∣ δ ( S ∪ S ′ ) ∣ = ∣ δ ( S ) ∣ + ∣ δ G 2 ( S ) ∣ < ∣ S ∣ + ∣ S ′ ∣ = ∣ S ∪ S ′ ∣ |\delta(S \cup S')|=|\delta(S)|+|\delta_{G_2}(S)|<|S|+|S'|=|S \cup S'| δ(SS)=δ(S)+δG2(S)<S+S=SS
这与G满足霍尔条件相悖。
综上所述, G 2 G_2 G2满足霍尔条件且 ∣ U 2 ∣ < k |U_2| < k U2<k,于是,根据归纳假设一定存在完美匹配 M 2 M_2 M2
M 1 ∪ M 2 M_1 \cup M_2 M1M2就是 G G G的一个完美匹配。
综上得证

匈牙利算法

匈牙利算法是一个高效求解最大匹配问题的算法。下面仅仅讨论满足 ∣ U ∣ = ∣ V ∣ |U|=|V| U=V的二部图 G = ( U , V , E ) G=(U, V, E) G=(U,V,E)
思考 G G G中的匹配 M M M
如果在 G G G的一条路中, M M M的边与 E − M E-M EM的边交错出现,则称该路为 M M M交错路
如果一条交错路的首尾两顶点都没有被 M M M匹配,则称该路为 M M M增广路。由定义知,增广路的长度一定为奇数。
将增广路上的各边进行属性进行互换操作(将本属于M的边从M中删除,本不属于M的边加入M),依然可以得到一个匹配,且新匹配比旧匹配M大1。

有以下命题成立

M 为最大匹配的充分必要条件是 M 中不含增广路 M为最大匹配的充分必要条件是M中不含增广路 M为最大匹配的充分必要条件是M中不含增广路

证明如下

必要性时显然的,下面证明充分性
假设M中没有增广路,但M不是最大匹配,令M’为最大匹配(之一)。
将M与M’合并,当M与M’中包含同一条边时,允许新图中包含平行边。
这个新图中的连通分支(不考虑孤立顶点)都是由M中的边与M’中的边交替出现所构成的路或圈。
如果时偶数长的路,或者圈,则其中包含的M边的条数和M’边的条数相等。
对于奇数长的路,如果首尾两条边时都是M中的边,则路中会多一条M的边;同样,如果首尾两条边都是M’的边,则路中会多一条M’的边。
因为|M’| > |M|,所以至少存在一条奇数长的路P且首尾两条边都是M’中的边。
由于P上的M‘的边都不在M中,所以P是交错路。又因为P的两端点都没有被M匹配,所以P是增广路。
与假设相悖,命题得证

匈牙利算法就是不断寻找当前匹配的增广路,步骤如下

  1. 选取任意一个匹配M
  2. 找出M的一条增广路。如果能找到,则用该增广路对M进行更新,重复该步骤;如果找不到,进入下一步骤
  3. 输出M

目前最重要的就是如何寻找增广路,下面是一个寻找增广路的算法AUGPATH

  1. U U U中找到一个没有被 M M M匹配到的顶点 u u u
  2. 将与 u u u相邻的 V V V中的顶点集合记为 T 1 T_1 T1
  3. 如果在 T 1 T_1 T1中存在没有被 M M M匹配到的顶点,则得到一条增广路 (长度为1)
  4. 如果 T 1 T_1 T1中的顶点都被 M M M匹配到了,则将与这些顶点相匹配的 U U U中的顶点的集合记为 S 1 S_1 S1
  5. 在V的顶点中,将与 S 1 S_1 S1中某个顶点相连接且不在 T 1 T_1 T1中的顶点的集合记为 T 2 T_2 T2
  6. 如果在 T 2 T_2 T2中存在未被M匹配的顶点,则得到一条增广路(长度为3)
  7. 以此类推,循环下去

这个算法可以有效的找到增广路
这个寻路算法停下的情况有两种

  1. 找到了增广路
  2. 没找到增广路, S i S_i Si为空,或者 T i T_i Ti为空

下面给出实现

from graph import BipartiteGraph

def augpath(G, M, vertex):
	T = []
	S = [[vertex]]
	E = dict()  # 储存寻找过程中的边
	i = -1
	visited = [vertex]
	end = None

	while True:
		# 寻找增广路
		T.append([])
		i += 1
		if not S[i]:
			break
		for vertex1 in S[i]:
			for vertex2 in G.ad_list(vertex1):
				if not vertex2 in visited:
					visited.append(vertex2)
					E[vertex2] = vertex1
					T[i].append(vertex2)
		for vertex2 in T[i]:
			if not vertex2 in M.V:
				end = vertex2
		if end is not None:
			break
		if not T[i]:
			break
		S.append([])
		for vertex2 in T[i]:
			for vertex1 in M.ad_list(vertex2):
				if not vertex1 in visited:
					visited.append(vertex1)
					E[vertex1] = vertex2
					S[i + 1].append(vertex1)

	if end is not None:
		# 拼凑出增广路
		point = end
		road = [end]
		while point != vertex:
			point = E[point]
			road.insert(0, point)
		return road

	return False

def hungarian(G: "BipartiteGraph"):
	M = BipartiteGraph()
	for vertex in G.U:
		# 找增广路
		if not vertex in M.U:
			# 如果vertex未被匹配,就以vertex为起点寻找增广路
			road = augpath(G, M, vertex)
			if road:
				# 找到后修改匹配M
				M.add_vertex_U(road[0])
				M.add_vertex_V(road[-1])
				for i in range(len(road)-1):
					if i % 2 == 0:
						M.add_edge((road[i], road[i+1]))
					else:
						M.remove_edge((road[i+1], road[i]))
	return M

用求解最大流问题的算法求解匹配问题

在原来的二部图G=(U, V, E)的基础上按照以下要求进行改造,构造出最大流问题中的有向图D

  • D是边赋权的有向图,权值代表容量
  • D的顶点集合为G的顶点集合U、V加上源点s与汇点t形成的集合
  • 从s出发到U中的每个顶点都连上一条弧,从V中的每个顶点出发到t都连上一条弧
  • U和V之间的弧集合与G的边集合E相同,并且弧的方向全部是从U指向V
  • 弧的容量全部为1

实现如下

from graph import BipartiteGraph, Edges, WeightesGraph 
from max_flow import ford_fulkerson

def max_match_by_max_flow(G):
	# 初始化D
	vertexes = list(G.vertexes.values())
	edges0 = [(u, v) for u in G.U for v in G.V if (u, v) in G.edges] + [("s", u) for u in G.U] + [(v, "t") for v in G.V]
	edges = Edges(*edges0, has_direction=True)
	weights = {edge: 1 for edge in edges0}  # 所有边的权重都为1
	D = WeightedGraph(vertexes, edges, weights)

	max_flow, _ = ford_fulkerson(D, "s", "t") # 用Ford_Fulkerson算法求D中的最大流
	M = BipartiteGraph() # 初始化最大匹配草图
	M.add_vertex_U(*G.U)
	M.add_vertex_V(*G.V)
	for edge in G.edges:
		# 根据max_flow,将边加入最大匹配草图
		if max_flow.weights[edge] == 1:
			M.add_edge(edge)

	return M

我建立了一个github公共库,用于存储这些代码,其中有图类的实现。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值