Skip to content

Commit 3b42bab

Browse files
committed
#34 Fix HopcroftKarp matching
1 parent 674327a commit 3b42bab

File tree

7 files changed

+282
-128
lines changed

7 files changed

+282
-128
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ TODO: implement trie compression.
184184
#### Matching
185185

186186
- [X] Max bipartite matching ([implementation](https://github.com/justcoding121/Advanced-Algorithms/blob/master/src/Advanced.Algorithms/Graph/Matching/BiPartiteMatching.cs) | [tests](https://github.com/justcoding121/Advanced-Algorithms/blob/master/tests/Advanced.Algorithms.Tests/Graph/Matching/BiPartiteMatching_Tests.cs)) using Edmonds Karp's improved Ford Fulkerson max flow algorithm
187-
- [ ] Max bipartite matching ([implementation](https://github.com/justcoding121/Advanced-Algorithms/blob/master/src/Advanced.Algorithms/Graph/Matching/HopcroftKarp.cs) | [tests](https://github.com/justcoding121/Advanced-Algorithms/blob/master/tests/Advanced.Algorithms.Tests/Graph/Matching/HopcroftKarp_Tests.cs)) using Hopcroft Karp algorithm
187+
- [X] Max bipartite matching ([implementation](https://github.com/justcoding121/Advanced-Algorithms/blob/master/src/Advanced.Algorithms/Graph/Matching/HopcroftKarp.cs) | [tests](https://github.com/justcoding121/Advanced-Algorithms/blob/master/tests/Advanced.Algorithms.Tests/Graph/Matching/HopcroftKarp_Tests.cs)) using Hopcroft Karp algorithm
188188

189189
#### Cut
190190

src/Advanced.Algorithms/Advanced.Algorithms.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323
<None Remove="Binary\**" />
2424
<None Remove="DynamicProgramming\**" />
2525
</ItemGroup>
26-
27-
<ItemGroup>
28-
<Compile Remove="Graph\Matching\HopcroftKarp.cs" />
29-
</ItemGroup>
3026

3127
<ItemGroup>
3228
<Compile Include="Binary\BaseConversion.cs" />

src/Advanced.Algorithms/Graph/Matching/BiPartiteMatching.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Advanced.Algorithms.Graph
88
/// <summary>
99
/// Compute Max BiParitite Edges using Ford-Fukerson algorithm.
1010
/// </summary>
11-
public class BiPartiteMatching<T>
11+
public class BiPartiteMatching<T>
1212
{
1313
readonly IBiPartiteMatchOperators<T> @operator;
1414
public BiPartiteMatching(IBiPartiteMatchOperators<T> @operator)
@@ -118,7 +118,7 @@ private static WeightedDiGraph<T, int> createFlowGraph(IGraph<T> graph,
118118
/// <summary>
119119
/// The match result object.
120120
/// </summary>
121-
public class MatchEdge<T>
121+
public class MatchEdge<T>
122122
{
123123
public T Source { get; }
124124
public T Target { get; }
@@ -128,6 +128,28 @@ public MatchEdge(T source, T target)
128128
Source = source;
129129
Target = target;
130130
}
131+
132+
public override bool Equals(object obj)
133+
{
134+
if(obj == this)
135+
{
136+
return true;
137+
}
138+
139+
var tgt = obj as MatchEdge<T>;
140+
141+
if(tgt is null)
142+
{
143+
return false;
144+
}
145+
146+
return tgt.Source.Equals(Source) && tgt.Target.Equals(Target);
147+
}
148+
149+
public override int GetHashCode()
150+
{
151+
return new { Source, Target }.GetHashCode();
152+
}
131153
}
132154

133155
/// <summary>

src/Advanced.Algorithms/Graph/Matching/HopcroftKarp.cs

Lines changed: 131 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class HopcroftKarpMatching<T>
1313
/// <summary>
1414
/// Returns a list of Max BiPartite Match Edges.
1515
/// </summary>
16-
public List<MatchEdge<T>> GetMaxBiPartiteMatching(IGraph<T> graph)
16+
public HashSet<MatchEdge<T>> GetMaxBiPartiteMatching(IGraph<T> graph)
1717
{
1818
//check if the graph is BiPartite by coloring 2 colors
1919
var mColorer = new MColorer<T, int>();
@@ -25,182 +25,195 @@ public List<MatchEdge<T>> GetMaxBiPartiteMatching(IGraph<T> graph)
2525
}
2626

2727
return getMaxBiPartiteMatching(graph, colorResult.Partitions);
28-
2928
}
3029

3130
/// <summary>
3231
/// Get Max Match from Given BiPartitioned Graph.
3332
/// </summary>
34-
private List<MatchEdge<T>> getMaxBiPartiteMatching(IGraph<T> graph,
33+
private HashSet<MatchEdge<T>> getMaxBiPartiteMatching(IGraph<T> graph,
3534
Dictionary<int, List<T>> partitions)
3635
{
37-
var leftMatch = new Dictionary<T, T>();
38-
var rightMatch = new Dictionary<T, T>();
36+
var matches = new HashSet<MatchEdge<T>>();
37+
38+
var leftToRightMatchEdges = new Dictionary<T, T>();
39+
var rightToLeftMatchEdges = new Dictionary<T, T>();
3940

41+
var freeVerticesOnRight = bfs(graph, partitions, leftToRightMatchEdges, rightToLeftMatchEdges);
4042
//while there is an augmenting Path
41-
while (bfs(graph, partitions, leftMatch, rightMatch))
43+
while (freeVerticesOnRight.Count > 0)
4244
{
43-
foreach (var vertex in partitions[2])
45+
var visited = new HashSet<T>();
46+
var paths = new HashSet<MatchEdge<T>>();
47+
48+
foreach (var vertex in freeVerticesOnRight)
4449
{
45-
if (!rightMatch.ContainsKey(vertex))
50+
var path = dfs(graph,
51+
leftToRightMatchEdges, rightToLeftMatchEdges, vertex, default, visited, true);
52+
53+
if (path != null)
4654
{
47-
var visited = new HashSet<T> { vertex };
55+
union(paths, path);
56+
}
57+
}
4858

49-
var pathResult = dfs(graph.GetVertex(vertex),
50-
leftMatch, rightMatch, visited, true);
59+
xor(matches, paths, leftToRightMatchEdges, rightToLeftMatchEdges);
5160

52-
//XOR remaining done here (partially done inside DFS)
53-
foreach (var pair in pathResult)
61+
freeVerticesOnRight = bfs(graph, partitions, leftToRightMatchEdges, rightToLeftMatchEdges);
62+
}
63+
64+
return matches;
65+
}
66+
67+
/// <summary>
68+
/// Returns list of free vertices on right if there is an augmenting Path from left to right.
69+
/// An augmenting path is a path which starts from a free vertex
70+
/// and ends at a free vertex via UnMatched (left -> right) and Matched (right -> left) edges alternatively.
71+
/// </summary>
72+
private List<T> bfs(IGraph<T> graph,
73+
Dictionary<int, List<T>> partitions,
74+
Dictionary<T, T> leftToRightMatchEdges, Dictionary<T, T> rightToLeftMatchEdges)
75+
{
76+
var queue = new Queue<T>();
77+
var visited = new HashSet<T>();
78+
79+
var freeVerticesOnRight = new List<T>();
80+
81+
foreach (var vertex in partitions[1])
82+
{
83+
//if this left vertex is free
84+
if (!leftToRightMatchEdges.ContainsKey(vertex))
85+
{
86+
queue.Enqueue(vertex);
87+
visited.Add(vertex);
88+
89+
while (queue.Count > 0)
90+
{
91+
var current = queue.Dequeue();
92+
93+
//unmatched edges left to right
94+
foreach (var leftToRightEdge in graph.GetVertex(current).Edges)
5495
{
55-
if (pair.isRight)
96+
if (visited.Contains(leftToRightEdge.TargetVertexKey))
5697
{
57-
rightMatch.Add(pair.A, pair.B);
58-
leftMatch.Add(pair.B, pair.A);
98+
continue;
99+
}
100+
101+
//checking if this right vertex is free
102+
if (!rightToLeftMatchEdges.ContainsKey(leftToRightEdge.TargetVertex.Key))
103+
{
104+
freeVerticesOnRight.Add(leftToRightEdge.TargetVertex.Key);
59105
}
60106
else
61107
{
62-
leftMatch.Add(pair.A, pair.B);
63-
rightMatch.Add(pair.B, pair.A);
108+
foreach (var rightToLeftEdge in leftToRightEdge.TargetVertex.Edges)
109+
{
110+
//matched edge right to left
111+
if (leftToRightMatchEdges.ContainsKey(rightToLeftEdge.TargetVertexKey)
112+
&& !visited.Contains(rightToLeftEdge.TargetVertexKey))
113+
{
114+
queue.Enqueue(rightToLeftEdge.TargetVertexKey);
115+
}
116+
}
64117
}
118+
119+
visited.Add(leftToRightEdge.TargetVertexKey);
65120
}
66121
}
67-
68122
}
69-
70123
}
71124

72-
//now gather all
73-
var result = new List<MatchEdge<T>>();
74-
75-
foreach (var item in leftMatch)
76-
{
77-
result.Add(new MatchEdge<T>(item.Key, item.Value));
78-
}
79-
return result;
125+
return freeVerticesOnRight;
80126
}
81127

82128
/// <summary>
83-
/// Trace Path for DFS
129+
/// Find an augmenting path that start from a given free vertex on right and ending
130+
/// at a free vertex on left. Return the matching edges along that path.
84131
/// </summary>
85-
private class PathResult
132+
private HashSet<MatchEdge<T>> dfs(IGraph<T> graph,
133+
Dictionary<T, T> leftToRightMatchEdges,
134+
Dictionary<T, T> rightToLeftMatchEdges,
135+
T current,
136+
T previous,
137+
HashSet<T> visited,
138+
bool currentIsRight)
86139
{
87-
public T A { get; }
88-
public T B { get; }
89-
public bool isRight { get; }
140+
var currentIsLeft = !currentIsRight;
90141

91-
public PathResult(T a, T b, bool isRight)
142+
if (visited.Contains(current))
92143
{
93-
A = a;
94-
B = b;
95-
this.isRight = isRight;
144+
return null;
96145
}
97-
}
98146

99-
private List<PathResult> dfs(IGraphVertex<T> current,
100-
Dictionary<T, T> leftMatch, Dictionary<T, T> rightMatch,
101-
HashSet<T> visitPath,
102-
bool isRightSide)
103-
{
104-
if (!leftMatch.ContainsKey(current.Key)
105-
&& !isRightSide)
147+
//free vertex on left found!
148+
if (currentIsLeft && !leftToRightMatchEdges.ContainsKey(current))
106149
{
107-
return new List<PathResult>();
150+
visited.Add(current);
151+
return new HashSet<MatchEdge<T>>() { new MatchEdge<T>(current, previous) };
108152
}
109153

110-
foreach (var edge in current.Edges)
154+
//right to left should be unmatched edges
155+
if (currentIsRight && !rightToLeftMatchEdges.ContainsKey(current))
111156
{
112-
//do not re-visit ancestors in current DFS tree
113-
if (visitPath.Contains(edge.TargetVertexKey))
114-
{
115-
continue;
116-
}
117-
118-
if (!visitPath.Contains(edge.TargetVertexKey))
119-
{
120-
visitPath.Add(edge.TargetVertexKey);
121-
}
122-
var pathResult = dfs(edge.TargetVertex, leftMatch, rightMatch, visitPath, !isRightSide);
123-
if (pathResult == null)
157+
foreach (var edge in graph.GetVertex(current).Edges)
124158
{
125-
continue;
159+
var result = dfs(graph, leftToRightMatchEdges, rightToLeftMatchEdges, edge.TargetVertexKey, current, visited, !currentIsRight);
160+
if (result != null)
161+
{
162+
result.Add(new MatchEdge<T>(edge.TargetVertexKey, current));
163+
visited.Add(current);
164+
return result;
165+
}
126166
}
167+
}
127168

128-
//XOR (partially done here by removing same edges)
129-
//other part of XOR (adding new ones) is done after DFS method is finished
130-
if (leftMatch.ContainsKey(current.Key)
131-
&& leftMatch[current.Key].Equals(edge.TargetVertexKey))
132-
{
133-
leftMatch.Remove(current.Key);
134-
rightMatch.Remove(edge.TargetVertexKey);
135-
}
136-
else if (rightMatch.ContainsKey(current.Key)
137-
&& rightMatch[current.Key].Equals(edge.TargetVertexKey))
138-
{
139-
rightMatch.Remove(current.Key);
140-
leftMatch.Remove(edge.TargetVertexKey);
141-
}
142-
else
169+
//left to right should be matched edges
170+
if (currentIsLeft && leftToRightMatchEdges.ContainsKey(current))
171+
{
172+
foreach (var edge in graph.GetVertex(current).Edges)
143173
{
144-
pathResult.Add(new PathResult(current.Key, edge.TargetVertexKey, isRightSide));
174+
var result = dfs(graph, leftToRightMatchEdges, rightToLeftMatchEdges, edge.TargetVertexKey, current, visited, !currentIsRight);
175+
if (result != null)
176+
{
177+
result.Add(new MatchEdge<T>(current, edge.TargetVertexKey));
178+
visited.Add(current);
179+
return result;
180+
}
145181
}
146-
147-
return pathResult;
148-
149182
}
150183

151184
return null;
152185
}
153-
154-
/// <summary>
155-
/// Returns true if there is an augmenting Path from left to right.
156-
/// An augmenting path is a path which starts from a free vertex
157-
/// and ends at a free vertex via Matched/UnMatched edges alternatively.
158-
/// </summary>
159-
private bool bfs(IGraph<T> graph,
160-
Dictionary<int, List<T>> partitions,
161-
Dictionary<T, T> leftMatch, Dictionary<T, T> rightMatch)
186+
187+
private void union(HashSet<MatchEdge<T>> paths, HashSet<MatchEdge<T>> path)
162188
{
163-
var queue = new Queue<T>();
164-
var visited = new HashSet<T>();
165-
166-
var leftGroup = new HashSet<T>();
167-
168-
foreach (var vertex in partitions[1])
189+
foreach (var item in path)
169190
{
170-
leftGroup.Add(vertex);
171-
//if vertex is free
172-
if (!leftMatch.ContainsKey(vertex))
191+
if (!paths.Contains(item))
173192
{
174-
queue.Enqueue(vertex);
175-
visited.Add(vertex);
193+
paths.Add(item);
176194
}
177195
}
196+
}
178197

179-
while (queue.Count > 0)
198+
private void xor(HashSet<MatchEdge<T>> matches, HashSet<MatchEdge<T>> paths,
199+
Dictionary<T, T> leftToRightMatchEdges, Dictionary<T, T> rightToLeftMatchEdges)
200+
{
201+
foreach (var item in paths)
180202
{
181-
var current = queue.Dequeue();
182-
183-
//if vertex is free
184-
if (!leftGroup.Contains(current) &&
185-
!rightMatch.ContainsKey(current))
203+
if (matches.Contains(item))
186204
{
187-
return true;
205+
matches.Remove(item);
206+
leftToRightMatchEdges.Remove(item.Source);
207+
rightToLeftMatchEdges.Remove(item.Target);
188208
}
189-
190-
foreach (var edge in graph.GetVertex(current).Edges)
209+
else
191210
{
192-
if (visited.Contains(edge.TargetVertexKey))
193-
{
194-
continue;
195-
}
196-
197-
queue.Enqueue(edge.TargetVertexKey);
198-
visited.Add(edge.TargetVertexKey);
211+
matches.Add(item);
212+
leftToRightMatchEdges.Add(item.Source, item.Target);
213+
rightToLeftMatchEdges.Add(item.Target, item.Source);
199214
}
200-
201215
}
202-
203-
return false;
204216
}
217+
205218
}
206219
}

tests/Advanced.Algorithms.Tests/Advanced.Algorithms.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
<Compile Include="Distributed\LRUCache_Tests.cs" />
119119
<Compile Include="Geometry\BentleyOttmann_Tests.cs" />
120120
<Compile Include="Geometry\PointRotation_Tests.cs" />
121+
<Compile Include="Graph\Matching\HopcroftKarp_Tests.cs" />
121122
<Compile Include="Graph\ShortestPath\AStar_Tests.cs" />
122123
<Compile Include="Graph\ShortestPath\TravellingSalesman_Tests.cs" />
123124
<Compile Include="Geometry\ClosestPointPair_Tests.cs" />

0 commit comments

Comments
 (0)