From c9f6d0ea7aa6803e257f153aa23c24b1678662b5 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Tue, 18 Mar 2025 22:07:07 +0530 Subject: [PATCH 1/4] feature: max bipartite matching init --- Headers/0003_Graph/0017_MaximumBipartiteMatching.h | 0 SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc | 0 SourceCodes/0003_Graph/CMakeLists.txt | 1 + Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc | 0 Tests/0003_Graph/CMakeLists.txt | 1 + 5 files changed, 2 insertions(+) create mode 100644 Headers/0003_Graph/0017_MaximumBipartiteMatching.h create mode 100644 SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc create mode 100644 Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc diff --git a/Headers/0003_Graph/0017_MaximumBipartiteMatching.h b/Headers/0003_Graph/0017_MaximumBipartiteMatching.h new file mode 100644 index 0000000..e69de29 diff --git a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc new file mode 100644 index 0000000..e69de29 diff --git a/SourceCodes/0003_Graph/CMakeLists.txt b/SourceCodes/0003_Graph/CMakeLists.txt index 1accab0..0e12aa0 100644 --- a/SourceCodes/0003_Graph/CMakeLists.txt +++ b/SourceCodes/0003_Graph/CMakeLists.txt @@ -16,6 +16,7 @@ set(0003GRAPH_SOURCES 0014_AllPairsShortestPathsJohnson.cc 0015_MaximumFlowFordFulkerson.cc 0016_MaximumFlowEdmondsKarp.cc + 0017_MaximumBipartiteMatching.cc ) diff --git a/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc b/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc new file mode 100644 index 0000000..e69de29 diff --git a/Tests/0003_Graph/CMakeLists.txt b/Tests/0003_Graph/CMakeLists.txt index 70c9814..cd89765 100644 --- a/Tests/0003_Graph/CMakeLists.txt +++ b/Tests/0003_Graph/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable( 0014_AllPairsShortestPathsJohnsonTest.cc 0015_MaximumFlowFordFulkersonTest.cc 0016_MaximumFlowEdmondsKarpTest.cc + 0017_MaximumBipartiteMatchingTest.cc ) target_link_libraries( From 1caea77eeb5439a67bf3838ad30ba70dd0a5e2ce Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Fri, 28 Mar 2025 22:28:28 +0530 Subject: [PATCH 2/4] feature: 0017 max bipartie match init --- .../0017_MaximumBipartiteMatching.h | 41 ++++ .../0017_MaximumBipartiteMatching.cc | 224 ++++++++++++++++++ 2 files changed, 265 insertions(+) diff --git a/Headers/0003_Graph/0017_MaximumBipartiteMatching.h b/Headers/0003_Graph/0017_MaximumBipartiteMatching.h index e69de29..b51ff8f 100644 --- a/Headers/0003_Graph/0017_MaximumBipartiteMatching.h +++ b/Headers/0003_Graph/0017_MaximumBipartiteMatching.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +using namespace std; + +namespace MaximumBipartiteMatching +{ + enum Color + { + WHITE = -1, + RED = 0, + BLUE = 1 + }; + + class Graph + { + private: + int _noOfVertices; + int _source; + int _sink; + int _maximumFlow; + bool _flagParallelEdges; + bool _isBipartite; + vector> _adjMatrix; + vector> _residualGraph; + vector _parent; + vector _visited; + vector _color; + vector> _matchings; + void ResolveAntiParallelEdges(); + void ColorGraph(); + void AddAdditionalEdges(); + bool BreadthFirstSearch(); + public: + void CreateGraph(int noOfVertices); + void PushDirectedEdge(int valueU, int valueV); + int FindMaximumBipartiteMatching(); + vector> GetMatchings(); + }; +} \ No newline at end of file diff --git a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc index e69de29..f4bdce2 100644 --- a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc +++ b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc @@ -0,0 +1,224 @@ +#include "../Headers/0003_Graph/0017_MaximumBipartiteMatching.h" +#include +#include +using namespace std; + +namespace MaximumBipartiteMatching +{ + // Graph Private Member Methods + void Graph::ResolveAntiParallelEdges() + { + int countParallelEdges = 0; + for (int i = 0; i < this->_noOfVertices; i++) + { + for (int j = 0; j < this->_noOfVertices; j++) + { + if (this->_adjMatrix[i][j] > 0 && this->_adjMatrix[j][i] > 0) + { + countParallelEdges++; + } + } + } + + // As i->j and j->i both edges has been counted, actual count is count = count / 2 + countParallelEdges /= 2; + + this->_flagParallelEdges = countParallelEdges > 0; + + // If there are no anti-parallel edges, no need to modify the adjMatrix + if (!this->_flagParallelEdges) + { + return; + } + + int newNoOfVertices = this->_noOfVertices + countParallelEdges; + + // Modifying the adjMatrix + for (auto& edge : this->_adjMatrix) + { + edge.resize(newNoOfVertices, 0); + } + int k = this->_noOfVertices; + this->_visited.resize(newNoOfVertices, false); + this->_parent.resize(newNoOfVertices, -1); + this->_adjMatrix.resize(newNoOfVertices, vector(newNoOfVertices, 0)); + + // Removing the anti-parallel edges by adding new nodes + for (int i = 0; i < this->_noOfVertices; i++) + { + for (int j = 0; j < this->_noOfVertices; j++) + { + if (this->_adjMatrix[i][j] > 0 && this->_adjMatrix[j][i] > 0) + { + this->_adjMatrix[i][k] = this->_adjMatrix[i][j]; + this->_adjMatrix[k][j] = this->_adjMatrix[i][j]; + this->_adjMatrix[i][j] = 0; + k++; + } + } + } + + // Updating the total no of vertices after modifying the adjMatrix + this->_noOfVertices = newNoOfVertices; + } + + void Graph::ColorGraph() + { + // Color of all the vertices are initialised to WHITE + fill(this->_color.begin(), this->_color.end(), WHITE); + + queue nodeQueue; + + for (int node = 0; node < this->_noOfVertices; node++) + { + if (this->_color[node] == WHITE) + { + this->_color[node] = RED; + nodeQueue.push(node); + + while (!nodeQueue.empty()) + { + int nodeU = nodeQueue.front(); + nodeQueue.pop(); + + for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) + { + if (this->_adjMatrix[nodeU][nodeV] != 0 && this->_color[nodeV] == WHITE) + { + this->_color[nodeV] = 1 - this->_color[nodeU]; + nodeQueue.push(nodeV); + } + else if (this->_color[nodeV] == this->_color[nodeU]) + { + this->_isBipartite = false; + return; + } + } + } + } + } + + this->_isBipartite = true; + return; + } + + void Graph::AddAdditionalEdges() + { + for (auto& edge : this->_residualGraph) + { + edge.resize(this->_noOfVertices, 0); + } + this->_parent.resize(this->_noOfVertices, -1); + this->_visited.resize(this->_noOfVertices, false); + this->_color.resize(this->_noOfVertices, WHITE); + this->_residualGraph.resize(this->_noOfVertices, vector(this->_noOfVertices, 0)); + for (int node = 0; node < this->_source; node++) + { + if (this->_color[node] == RED) + { + this->_residualGraph[this->_source][node] = 1; + } + else if (this->_color[node] == BLUE) + { + this->_residualGraph[node][this->_sink] = 1; + } + } + } + + bool Graph::BreadthFirstSearch() + { + // Resetting the visited values + fill(this->_visited.begin(), this->_visited.end(), false); + + // Resetting the parent values + fill(this->_parent.begin(), this->_parent.end(), -1); + + queue nodeQueue; + nodeQueue.push(this->_source); + this->_visited[this->_source] = true; + + while (!nodeQueue.empty()) + { + int nodeU = nodeQueue.front(); + nodeQueue.pop(); + + for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) + { + if (!this->_visited[nodeV] && this->_residualGraph[nodeU][nodeV] > 0) + { + this->_parent[nodeV] = nodeU; + this->_visited[nodeV] = true; + nodeQueue.push(nodeV); + } + } + } + + // Returning the visited value of the sink vertex, initially it was set to false + return this->_visited[this->_sink]; + } + + // Graph Public Member Methods + void Graph::CreateGraph(int noOfVertices) + { + this->_noOfVertices = noOfVertices; + this->_maximumFlow = 0; + this->_flagParallelEdges = false; + this->_adjMatrix = vector>(this->_noOfVertices, vector(this->_noOfVertices, 0)); + this->_parent = vector(this->_noOfVertices, -1); + this->_visited = vector(this->_noOfVertices, false); + this->_color = vector(this->_noOfVertices, WHITE); + } + + void Graph::PushDirectedEdge(int valueU, int valueV) + { + this->_adjMatrix[valueU][valueV] = 1; + } + + int Graph::FindMaximumBipartiteMatching() + { + // Resolving all the parallel edges if present + this->ResolveAntiParallelEdges(); + this->_residualGraph = this->_adjMatrix; + + this->ColorGraph(); + + this->_source = this->_noOfVertices; + this->_noOfVertices++; + this->_sink = this->_noOfVertices; + this->_noOfVertices++; + + this->AddAdditionalEdges(); + + // While there exists a path p from source to sink in the residual network G' + while (this->BreadthFirstSearch()) + { + int augmentedPathFlow = 1; + + for (int nodeV = this->_sink; nodeV != this->_source; nodeV = this->_parent[nodeV]) + { + int nodeU = this->_parent[nodeV]; + this->_residualGraph[nodeU][nodeV] -= augmentedPathFlow; + this->_residualGraph[nodeV][nodeU] += augmentedPathFlow; + } + this->_maximumFlow += augmentedPathFlow; + } + + return this->_maximumFlow; + } + + vector> Graph::GetMatchings() + { + for (int nodeU = 0; nodeU < this->_noOfVertices; nodeU++) + { + for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) + { + if (this->_residualGraph[nodeV][nodeU] == 1) + { + this->_matchings.push_back({ nodeU, nodeV }); + } + } + } + + return this->_matchings; + } +} \ No newline at end of file From 019820b91fc0fdb5c3461dd7e26e27169ff32f37 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Sat, 29 Mar 2025 22:17:49 +0530 Subject: [PATCH 3/4] fix-test: 0017 max bipt. match, init test --- .../0017_MaximumBipartiteMatching.cc | 16 ++++++--- .../0017_MaximumBipartiteMatchingTest.cc | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc index f4bdce2..6ef11ce 100644 --- a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc +++ b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc @@ -83,12 +83,16 @@ namespace MaximumBipartiteMatching for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) { - if (this->_adjMatrix[nodeU][nodeV] != 0 && this->_color[nodeV] == WHITE) + if (nodeU == nodeV) + { + continue; + } + else if (this->_residualGraph[nodeU][nodeV] != 0 && this->_color[nodeV] == WHITE) { this->_color[nodeV] = 1 - this->_color[nodeU]; nodeQueue.push(nodeV); } - else if (this->_color[nodeV] == this->_color[nodeU]) + else if (this->_residualGraph[nodeU][nodeV] != 0 && this->_color[nodeV] == this->_color[nodeU]) { this->_isBipartite = false; return; @@ -208,11 +212,13 @@ namespace MaximumBipartiteMatching vector> Graph::GetMatchings() { - for (int nodeU = 0; nodeU < this->_noOfVertices; nodeU++) + for (int nodeU = 0; nodeU < this->_adjMatrix.size(); nodeU++) { - for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) + for (int nodeV = 0; nodeV < this->_adjMatrix.size(); nodeV++) { - if (this->_residualGraph[nodeV][nodeU] == 1) + if ((nodeU != this->_source || nodeU != this->_sink || nodeV != this->_source || nodeV != this->_sink) + && + (this->_adjMatrix[nodeU][nodeV] - this->_residualGraph[nodeU][nodeV]) == 1) { this->_matchings.push_back({ nodeU, nodeV }); } diff --git a/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc b/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc index e69de29..506e2df 100644 --- a/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc +++ b/Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc @@ -0,0 +1,35 @@ +#include +#include "../Headers/0003_Graph/0017_MaximumBipartiteMatching.h" +#include "../0000_CommonUtilities/UnitTestHelper.h" + +namespace MaximumBipartiteMatching +{ + TEST(MaximumBipartiteMatching, SimpleGraph) + { + // Arrange + Graph graph; + UnitTestHelper unitTestHelper; + int noOfVertices = 9; + int expectedMaximumMatching = 3; + string expectedMatchings = "[0 1][2 6][3 5]"; + + // Act + graph.CreateGraph(noOfVertices); + + graph.PushDirectedEdge(0, 1); + graph.PushDirectedEdge(2, 1); + graph.PushDirectedEdge(2, 6); + graph.PushDirectedEdge(3, 5); + graph.PushDirectedEdge(3, 6); + graph.PushDirectedEdge(3, 7); + graph.PushDirectedEdge(4, 6); + graph.PushDirectedEdge(8, 6); + + int actualMaximumMatching = graph.FindMaximumBipartiteMatching(); + vector> actualMatchings = graph.GetMatchings(); + + // Assert + ASSERT_EQ(expectedMaximumMatching, actualMaximumMatching); + ASSERT_EQ(expectedMatchings, unitTestHelper.SerializeVectorToString(actualMatchings)); + } +} \ No newline at end of file From 29ef6ce4270a50bff92fe35083de24d0635c7ff3 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Sun, 30 Mar 2025 21:57:52 +0530 Subject: [PATCH 4/4] docs: added comments for 0017 max bpt match --- .../0017_MaximumBipartiteMatching.cc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc index 6ef11ce..1e873a5 100644 --- a/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc +++ b/SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc @@ -62,38 +62,52 @@ namespace MaximumBipartiteMatching this->_noOfVertices = newNoOfVertices; } + // This method is used to color the vertices of the graph to determine if the given graph is bipartite or not void Graph::ColorGraph() { // Color of all the vertices are initialised to WHITE fill(this->_color.begin(), this->_color.end(), WHITE); + // Queue to hold the vertices queue nodeQueue; for (int node = 0; node < this->_noOfVertices; node++) { + // Check if the node is already not colored if (this->_color[node] == WHITE) { + // The color of the node is set to RED this->_color[node] = RED; + + // The node is inserted into the queue nodeQueue.push(node); + // Using BFS method to color all the vertices while (!nodeQueue.empty()) { int nodeU = nodeQueue.front(); nodeQueue.pop(); + // Iterating over G.Adj[nodeU] for (int nodeV = 0; nodeV < this->_noOfVertices; nodeV++) { + // As there are no self loops, continue if (nodeU == nodeV) { continue; } + // Check if there is an edge u --> v and nodeV is not colored yet else if (this->_residualGraph[nodeU][nodeV] != 0 && this->_color[nodeV] == WHITE) { + // Set the color of nodeV opposite of nodeU this->_color[nodeV] = 1 - this->_color[nodeU]; + // Insert the nodeV into the queue nodeQueue.push(nodeV); } + // Check if there is an edge u --> v and nodeV is of same color as nodeU else if (this->_residualGraph[nodeU][nodeV] != 0 && this->_color[nodeV] == this->_color[nodeU]) { + // Set the _isBipartite flag to false and return this->_isBipartite = false; return; } @@ -102,26 +116,38 @@ namespace MaximumBipartiteMatching } } + // If the above operation completes without returning yet that indicates the graph is bipartite + // Set the _isBipartite flag to true and return this->_isBipartite = true; return; } + // This method is used to create the additional edges + // from the source vertex to the RED colored vertices and + // from the BLUE colored vertices to the sink vertex void Graph::AddAdditionalEdges() { + // Resizing the residual graph to accomodate space for the new edges for (auto& edge : this->_residualGraph) { edge.resize(this->_noOfVertices, 0); } + this->_parent.resize(this->_noOfVertices, -1); this->_visited.resize(this->_noOfVertices, false); this->_color.resize(this->_noOfVertices, WHITE); this->_residualGraph.resize(this->_noOfVertices, vector(this->_noOfVertices, 0)); + + // Creating the additional edges for (int node = 0; node < this->_source; node++) { + // From source vertex --> RED colored vertices if (this->_color[node] == RED) { this->_residualGraph[this->_source][node] = 1; } + + // From BLUE colored vertices --> sink vertex else if (this->_color[node] == BLUE) { this->_residualGraph[node][this->_sink] = 1; @@ -129,6 +155,7 @@ namespace MaximumBipartiteMatching } } + // Implementation of BreadthFirstSearch for EdmondsKarp algorithm to find the path from source to sink bool Graph::BreadthFirstSearch() { // Resetting the visited values @@ -198,6 +225,8 @@ namespace MaximumBipartiteMatching { int augmentedPathFlow = 1; + // No need to find the minimum amount of augmentedPathFlow as like standard EdmondsKarp algorithm + // as here capacity of each edges is 1 for (int nodeV = this->_sink; nodeV != this->_source; nodeV = this->_parent[nodeV]) { int nodeU = this->_parent[nodeV]; @@ -210,12 +239,16 @@ namespace MaximumBipartiteMatching return this->_maximumFlow; } + // This method is used for finding the matchings vector> Graph::GetMatchings() { for (int nodeU = 0; nodeU < this->_adjMatrix.size(); nodeU++) { for (int nodeV = 0; nodeV < this->_adjMatrix.size(); nodeV++) { + // Check if the nodeU and nodeV are not source or sink + // and there is a flow of 1 unit from nodeU --> nodeV + // which means nodeU --> nodeV is being used for the maximum flow (maximum matching) if ((nodeU != this->_source || nodeU != this->_sink || nodeV != this->_source || nodeV != this->_sink) && (this->_adjMatrix[nodeU][nodeV] - this->_residualGraph[nodeU][nodeV]) == 1)