1
+ import java .util .*;
2
+
3
+ public class BipartiteMatching {
4
+
5
+ class Edge {
6
+ private static final int defaultEdgeCapacity = 1 ; //This Flow network is for bipartite matching so the default capacity is always 1
7
+ private int fromVertex ; //an edge is composed of 2 vertices
8
+ private int toVertex ;
9
+ private int capacity ; //edges also have a capacity & a flow
10
+ private int flow ;
11
+
12
+ //Overloaded constructor to create a generic edge with a default capacity
13
+ public Edge (int fromVertex , int toVertex ){
14
+ this (fromVertex , toVertex , defaultEdgeCapacity );
15
+ }
16
+
17
+ public Edge (int fromVertex , int toVertex , int capacity ){
18
+ this .fromVertex = fromVertex ;
19
+ this .toVertex = toVertex ;
20
+ this .capacity = capacity ;
21
+ }
22
+
23
+ //Given an end-node, Returns the other end-node (completes the edge)
24
+ public int getOtherEndNode (int vertex ){
25
+ if (vertex ==fromVertex ){
26
+ return toVertex ;
27
+ }
28
+ return fromVertex ;
29
+ }
30
+
31
+ public int getCapacity (){
32
+ return capacity ;
33
+ }
34
+
35
+ public int getFlow (){
36
+ return flow ;
37
+ }
38
+
39
+ public int residualCapacityTo (int vertex ){
40
+ if (vertex ==fromVertex ){
41
+ return flow ;
42
+ }
43
+ return (capacity -flow );
44
+ }
45
+
46
+ public void increaseFlowTo (int vertex , int changeInFlow ){
47
+ if (vertex ==fromVertex ){
48
+ flow = flow -changeInFlow ;
49
+ }
50
+ else {
51
+ flow = flow +changeInFlow ;
52
+ }
53
+ }
54
+
55
+ //Prints edge using Array indexes, not human readable ID's like "S" or "T"
56
+ @ Override
57
+ public String toString (){
58
+ return "(" + fromVertex +" --> " +toVertex + ")" ;
59
+ }
60
+ }
61
+
62
+
63
+
64
+ private ArrayList <ArrayList <Edge >> graph ; //Graph is represented as an ArrayList of Edges
65
+ private ArrayList <String > getStringVertexIdFromArrayIndex ; //convert between array indexes (starting from 0) & human readable vertex names
66
+ private int vertexCount ; //How many vertices are in the graph
67
+
68
+ //These fields are updated by fordFulkersonMaxFlow and when finding augmentation paths
69
+ private Edge [] edgeTo ;
70
+ private boolean [] isVertexMarked ; //array of all vertices, updated each time an augmentation path is found
71
+ private int flow ;
72
+
73
+ //Constructor initializes graph edge list with number of vertexes, string equivalents for array indexes & adds empty ArrayLists to the graph for how many vertices ther are
74
+ public BipartiteMatching (int vertexCount , ArrayList <String > getStringVertexIdFromArrayIndex ){
75
+ this .vertexCount = vertexCount ;
76
+ this .getStringVertexIdFromArrayIndex = getStringVertexIdFromArrayIndex ;
77
+
78
+ graph = new ArrayList <>(vertexCount ); //Populate graph with empty ArrayLists for each vertex
79
+ for (int i =0 ; i <vertexCount ; ++i ){
80
+ graph .add (new ArrayList <>());
81
+ }
82
+ }
83
+
84
+ public void addEdge (int fromVertex , int toVertex ){
85
+ Edge newEdge = new Edge (fromVertex , toVertex ); //create new edge between 2 vertices
86
+ graph .get (fromVertex ).add (newEdge ); //Undirected bipartie graph, so add edge in both directions
87
+ graph .get (toVertex ).add (newEdge );
88
+ }
89
+
90
+ //Adds edges from the source to all vertices in the left half
91
+ public void connectSourceToLeftHalf (int source , int [] leftHalfVertices ){
92
+ for (int vertexIndex : leftHalfVertices ){
93
+ // System.out.println("addEdge(source, vertexIndex) = ("+source+", "+vertexIndex+")");
94
+ this .addEdge (source , vertexIndex );
95
+ }
96
+ }
97
+
98
+ //Adds edges from all vertices in right half to sink
99
+ public void connectSinkToRightHalf (int sink , int [] rightHalfVertices ){
100
+ for (int vertexIndex : rightHalfVertices ){
101
+ // System.out.println("addEdge(vertexIndex, sink) = ("+vertexIndex+", "+sink+")");
102
+ this .addEdge (vertexIndex , sink );
103
+ }
104
+ }
105
+
106
+ //Finds max flow / min cut of a graph
107
+ public void fordFulkersonMaxFlow (int source , int sink ){
108
+ edgeTo = new Edge [vertexCount ];
109
+ while (existsAugmentingPath (source , sink )){
110
+ int flowIncrease = 1 ; //default value is 1 since it's a bipartite matching problem with capacities = 1
111
+
112
+ System .out .print ("Matched Vertices " +(getStringVertexIdFromArrayIndex .get ( edgeTo [sink ].getOtherEndNode (sink ) ) )+" & " ); //Print the 1st vertex of the pair
113
+ //Loop over The path from source to sink. (Update max flow & print the other matched vertex)
114
+ for (int i =sink ; i !=source ; i =edgeTo [i ].getOtherEndNode (i )){
115
+ //Loop stops when i reaches the source, so print out the vertex in the path that comes right before the source
116
+ if (edgeTo [i ].getOtherEndNode (i )==source ){
117
+ System .out .println (getStringVertexIdFromArrayIndex .get (i )); //use human readable vertex ID's
118
+ }
119
+ flowIncrease = Math .min (flowIncrease , edgeTo [i ].residualCapacityTo (i ));
120
+ }
121
+
122
+ //Update Residual Capacities
123
+ for (int i =sink ; i !=source ; i =edgeTo [i ].getOtherEndNode (i )){
124
+ edgeTo [i ].increaseFlowTo (i , flowIncrease );
125
+ }
126
+ flow +=flowIncrease ;
127
+ }
128
+ System .out .println ("\n Maximum pairs matched = " +flow );
129
+ }
130
+
131
+ //Calls dfs to find an augmentation path & check if it reached the sink
132
+ public boolean existsAugmentingPath (int source , int sink ){
133
+ isVertexMarked = new boolean [vertexCount ]; //recreate array of visited nodes each time searching for a path
134
+ isVertexMarked [source ] = true ; //visit the source
135
+
136
+ // System.out.print("Augmenting Path : S ");
137
+ depthFirstSearch (source , sink ); //attempts to find path from source to sink & updates isVertexMarked
138
+ // System.out.print("T ");
139
+
140
+ return isVertexMarked [sink ]; //if it reached the sink, then a path was found
141
+ }
142
+
143
+ public void depthFirstSearch (int v , int sink ){
144
+ if (v ==sink ){ //No point in finding a path if the starting vertex is already at the sink
145
+ return ;
146
+ }
147
+
148
+ for (Edge edge : graph .get (v )){ //loop over all edges in the graph
149
+ int otherEndNode = edge .getOtherEndNode (v );
150
+ if (!isVertexMarked [otherEndNode ] && edge .residualCapacityTo (otherEndNode )>0 ){ //if otherEndNode is unvisited AND if the residual capacity exists at the otherEndNode
151
+ // System.out.print( getStringVertexIdFromArrayIndex.get(otherEndNode) +" ");
152
+ edgeTo [otherEndNode ] = edge ; //update next link in edge chain
153
+ isVertexMarked [otherEndNode ] = true ; //visit the node
154
+ depthFirstSearch (otherEndNode , sink ); //recursively continue exploring
155
+ }
156
+ }
157
+ }
158
+
159
+
160
+ public static void main (String [] args ){
161
+ int vertexCount = 10 ;
162
+ int vertexCountIncludingSourceAndSink = vertexCount +2 ;
163
+ //convert between array indexes (starting from 0) & human readable vertex names
164
+ ArrayList <String > getStringVertexIdFromArrayIndex = new ArrayList <String >(Arrays .asList ("1" , "2" , "3" , "4" , "5" , "1'" , "2'" , "3'" , "4'" , "5'" ));
165
+ getStringVertexIdFromArrayIndex .add ("S" ); //Add source & sink as last 2 items in the list
166
+ getStringVertexIdFromArrayIndex .add ("T" );
167
+
168
+ int source = vertexCount ; //source & sink as array indexes
169
+ int sink = vertexCount +1 ;
170
+
171
+ //These must be consecutive indexes. rightHalfVertices starts with the next integer after the last item in leftHaldVertices
172
+ int [] leftHalfVertices ={0 , 1 , 2 , 3 , 4 }; //source is connected to these vertices
173
+ int [] rightHalfVertices = {5 , 6 , 7 , 8 , 9 }; //sink is connected to these vertices
174
+
175
+ BipartiteMatching graph1BipartiteMatcher = new BipartiteMatching (vertexCountIncludingSourceAndSink , getStringVertexIdFromArrayIndex );
176
+ graph1BipartiteMatcher .addEdge (0 , 5 );
177
+ graph1BipartiteMatcher .addEdge (0 , 6 );
178
+ graph1BipartiteMatcher .addEdge (1 , 6 );
179
+ graph1BipartiteMatcher .addEdge (2 , 5 );
180
+ graph1BipartiteMatcher .addEdge (2 , 7 );
181
+ graph1BipartiteMatcher .addEdge (2 , 8 );
182
+ graph1BipartiteMatcher .addEdge (3 , 6 );
183
+ graph1BipartiteMatcher .addEdge (3 , 9 );
184
+ graph1BipartiteMatcher .addEdge (4 , 6 );
185
+ graph1BipartiteMatcher .addEdge (4 , 9 );
186
+
187
+ graph1BipartiteMatcher .connectSourceToLeftHalf (source , leftHalfVertices );
188
+ graph1BipartiteMatcher .connectSinkToRightHalf (sink , rightHalfVertices );
189
+
190
+ System .out .println ("Running Bipartite Matching on Graph 1" );
191
+ graph1BipartiteMatcher .fordFulkersonMaxFlow (source , sink );
192
+ System .out .println ("\n " );
193
+
194
+
195
+ //Graph 2
196
+ int vertexCount2 = 11 ;
197
+ int vertexCountIncludingSourceAndSink2 = vertexCount2 +2 ;
198
+ //convert between array indexes (starting from 0) & human readable vetex names
199
+ ArrayList <String > getStringVertexIdFromArrayIndex2 = new ArrayList <String >(Arrays .asList ("1" , "2" , "3" , "4" , "5" , "6" , "1'" , "2'" , "3'" , "4'" , "5'" ));
200
+ getStringVertexIdFromArrayIndex2 .add ("S" ); //Add source & sink as last 2 items in the list
201
+ getStringVertexIdFromArrayIndex2 .add ("T" );
202
+
203
+ int source2 = vertexCount2 ; //add a source & sink (these are array indexes)
204
+ int sink2 = vertexCount2 +1 ;
205
+
206
+ //these must be consecutive indexes. rightHalfVertices starts with the next integer after the last item in leftHaldVertices
207
+ int [] leftHalfVertices2 ={0 , 1 , 2 , 3 , 4 , 5 }; //source is connected to these vertices
208
+ int [] rightHalfVertices2 = {6 , 7 , 8 , 9 , 10 }; //sink is connected to these vertices
209
+
210
+ BipartiteMatching graph2BipartiteMatcher = new BipartiteMatching (vertexCountIncludingSourceAndSink2 , getStringVertexIdFromArrayIndex2 );
211
+ graph2BipartiteMatcher .addEdge (0 , 7 );
212
+ graph2BipartiteMatcher .addEdge (1 , 6 );
213
+ graph2BipartiteMatcher .addEdge (2 , 7 );
214
+ graph2BipartiteMatcher .addEdge (3 , 6 );
215
+ graph2BipartiteMatcher .addEdge (3 , 8 );
216
+ graph2BipartiteMatcher .addEdge (3 , 9 );
217
+ graph2BipartiteMatcher .addEdge (4 , 7 );
218
+ graph2BipartiteMatcher .addEdge (4 , 9 );
219
+ graph2BipartiteMatcher .addEdge (5 , 8 );
220
+ graph2BipartiteMatcher .addEdge (5 , 10 );
221
+
222
+ graph2BipartiteMatcher .connectSourceToLeftHalf (source2 , leftHalfVertices2 );
223
+ graph2BipartiteMatcher .connectSinkToRightHalf (sink2 , rightHalfVertices2 );
224
+
225
+ System .out .println ("Running Bipartite Matching on Graph 2" );
226
+ graph2BipartiteMatcher .fordFulkersonMaxFlow (source2 , sink2 );
227
+ }
228
+ }
0 commit comments