The Hungarian matching algorithm , also called the Kuhn-Munkres algorithm, is a O(∣V∣3)algorithm that can be used to find maximum-weight matchings in bipartite graphs , which is sometimes called the assignment problem . A bipartite graph can easily be represented by an adj matrix, where the weights of edges are the entries. Thinking about the graph in terms of an adjacency matrix is useful for the Hungarian algorithm.
The Hungarian Algorithm for Graphs
Given: the labeling l, an equality graph Gl=(V,El), an initial matching M in Gl, and an unmatched vertex u∈V and u∈/M
Augmenting the matching
A path is augmenting for M in Gl if it alternates between edges in the matching and edges not in the matching, and the first and last vertices are free vertices, or unmatched, in M. We will keep track of a candidate augmenting path starting at the vertex u.
If the algorithm finds an unmatched vertex v, add on to the existing augmenting path p by adding the u to v segment.
Flip the matching by replacing the edges in M with the edges in the augmenting path that are not in M (in other words, the edges in El−M).
Improving the labeling
S⊆X and T⊆Y, where S and T represent the candidate augmenting alternating path between the matching and the edges not in the matching.
Let Nl(S) be the neighbors to each node that is in S along edges in El such that Nl(S)={v∣∀u∈S:(u,v)∈El}.
If Nl(S)=T, then we cannot increase the size of the alternating path (and therefore can't further augment), so we need to improve the labeling.
Let δl be the minimum of l(u)+l(v)−w(u,v) over all of the u∈S and v∈/T.
Improve the labeling l to l′ :
If r∈S, then l′(r)=l(r)−δl,
If r∈T, then l′(r)=l(r)+δl.
If r∈/S and r∈/T, then l′(r)=l(r).
l′ is a valid labeling and El⊂El′.
Putting it all together: The Hungarian Algorithm
Start with some matching M, a valid labeling l, where l is defined as the labelling ∀x∈X,y∈Y∣l(y)=0,l(x)=maxy∈Y(w(x,y)).
Do these steps until a perfect matching is found (when M is perfect):
(a) Look for an augmenting path in M.
(b) If an augmenting path does not exist, improve the labeling and then go back to step (a).
TOLERANCE = 1e-6# everything below is considered zero MAX = 999999 defimproveLabels(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
defimproveMatching(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
defslack(u,v):return lu[u]+lv[v]-w[u][v]
defaugment(): """ augment the matching, possibly improving the lablels on the way. """ whileTrue: # 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 notin T]) # assert u in S # assert val > - TOLERANCE if val > TOLERANCE: improveLabels(val) # now we are sure that (u,v) is saturated assertabs(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, assertnot u1 in S S[u1] = True# ... add endpoint to tree for v in V: # maintain minSlack ifnot 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
defmaxWeightMatching(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) V = range(n) U = range(n) # initialize the feasible vertex label. lu = [ max([w[u][v] for v in V]) for u in U] lv = [ 0for v in V] Mu = {} Mv = {} # choose a unmatched vertex u0 whilelen(Mu)<n: free = [u for u in U if u notin Mu] u0 = free[0] S = {u0: True} T = {} minSlack = [[slack(u0,v), u0] for v in V] augment()