Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3b42bab

Browse files
#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-
publicPathResult(Ta,Tb,boolisRight)
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)

AltStyle によって変換されたページ (->オリジナル) /