-
Notifications
You must be signed in to change notification settings - Fork 14
/
kuhnMunkres.py
executable file
·122 lines (107 loc) · 4.32 KB
/
kuhnMunkres.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/python
# ecole polytechnique - c.durr - 2009
# Kuhn-Munkres, The hungarian algorithm. Complexity O(n^3)
# Computes a max weight perfect matching in a bipartite graph
# for min weight matching, simply negate the weights.
""" Global variables:
n = number of vertices on each side
U,V vertex sets
lu,lv are the labels of U and V resp.
the matching is encoded as
- a mapping Mu from U to V,
- and Mv from V to U.
The algorithm repeatedly builds an alternating tree, rooted in a
free vertex u0. S is the set of vertices in U covered by the tree.
For every vertex v, T[v] is the parent in the tree and Mv[v] the
child.
The algorithm maintains minSlack, s.t. for every vertex v not in
T, minSlack[v]=(val,u1), where val is the minimum slack
lu[u]+lv[v]-w[u][v] over u in S, and u1 is the vertex that
realizes this minimum.
Complexity is O(n^3), because there are n iterations in
maxWeightMatching, and each call to augment costs O(n^2). This is
because augment() makes at most n iterations itself, and each
updating of minSlack costs O(n).
"""
TOLERANCE = 1e-6 # everything below is considered zero
def improveLabels(val):
""" change the labels, and maintain minSlack.
"""
for u in S:
lu[u] -= val
for v in V:
if v in T:
lv[v] += val
else:
minSlack[v][0] -= val
def improveMatching(v):
""" apply the alternating path from v to the root in the tree.
"""
u = T[v]
if u in Mu:
improveMatching(Mu[u])
Mu[u] = v
Mv[v] = u
def slack(u,v): return lu[u]+lv[v]-w[u][v]
def augment():
""" augment the matching, possibly improving the lablels on the way.
"""
while True:
# select edge (u,v) with u in S, v not in T and min slack
((val, u), v) = min([(minSlack[v], v) for v in V if v not in T])
assert u in S
assert val > - TOLERANCE
if val > TOLERANCE:
improveLabels(val)
# now we are sure that (u,v) is saturated
assert abs(slack(u,v)) < TOLERANCE # test zero slack with tolerance
T[v] = u # add (u,v) to the tree
if v in Mv:
u1 = Mv[v] # matched edge,
assert not u1 in S
S[u1] = True # ... add endpoint to tree
for v in V: # maintain minSlack
if not v in T and minSlack[v][0] > slack(u1,v):
minSlack[v] = [slack(u1,v), u1]
else:
improveMatching(v) # v is a free vertex
return
def maxWeightMatching(weights):
""" given w, the weight matrix of a complete bipartite graph,
returns the mappings Mu : U->V ,Mv : V->U encoding the matching
as well as the value of it.
"""
global U,V,S,T,Mu,Mv,lu,lv, minSlack, w
w = weights
n = len(w)
U = V = range(n)
lu = [ max([w[u][v] for v in V]) for u in U] # start with trivial labels
lv = [ 0 for v in V]
Mu = {} # start with empty matching
Mv = {}
while len(Mu)<n:
free = [u for u in V if u not in Mu] # choose free vertex u0
u0 = free[0]
S = {u0: True} # grow tree from u0 on
T = {}
minSlack = [[slack(u0,v), u0] for v in V]
augment()
# val. of matching is total edge weight
val = sum(lu)+sum(lv)
return (Mu, Mv, val)
# a small example
#print maxWeightMatching([[1,2,3,4],[2,4,6,8],[3,6,9,12],[4,8,12,16]])
# read from standard input a line with n
# then n*n lines with u,v,w[u][v]
if __name__=='__main__':
# n = int(raw_input())
# w = [[0 for v in range(n)] for u in range(n)]
# for _ in range(n*n):
# u,v,w[u][v] = map(int, raw_input().split())
# print maxWeightMatching(w)
# print maxWeightMatching([[-31, -3, -2, -31, -31], [-31, -31, -2, -4, -31], [-31, -31, -31, -1, -5], [-31, -31, -31, -31, -6], [-31, -31, -31, -31, -31]])
print maxWeightMatching([[ 7, 53, 183, 439, 863],
[497, 383, 563, 79, 973],
[287, 63, 343, 169, 583],
[627, 343, 773, 959, 943],
[767, 473, 103, 699, 303]])