Skip to content

Release #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Headers/0003_Graph/0017_MaximumBipartiteMatching.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include<map>
#include<vector>
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<vector<int>> _adjMatrix;
vector<vector<int>> _residualGraph;
vector<int> _parent;
vector<bool> _visited;
vector<int> _color;
vector<vector<int>> _matchings;
void ResolveAntiParallelEdges();
void ColorGraph();
void AddAdditionalEdges();
bool BreadthFirstSearch();
public:
void CreateGraph(int noOfVertices);
void PushDirectedEdge(int valueU, int valueV);
int FindMaximumBipartiteMatching();
vector<vector<int>> GetMatchings();
};
}
263 changes: 263 additions & 0 deletions SourceCodes/0003_Graph/0017_MaximumBipartiteMatching.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#include "../Headers/0003_Graph/0017_MaximumBipartiteMatching.h"
#include<climits>
#include<queue>
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<int>(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;
}

// 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<int> 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;
}
}
}
}
}

// 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<int>(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;
}
}
}

// Implementation of BreadthFirstSearch for EdmondsKarp algorithm to find the path from source to sink
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<int> 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<vector<int>>(this->_noOfVertices, vector<int>(this->_noOfVertices, 0));
this->_parent = vector<int>(this->_noOfVertices, -1);
this->_visited = vector<bool>(this->_noOfVertices, false);
this->_color = vector<int>(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;

// 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];
this->_residualGraph[nodeU][nodeV] -= augmentedPathFlow;
this->_residualGraph[nodeV][nodeU] += augmentedPathFlow;
}
this->_maximumFlow += augmentedPathFlow;
}

return this->_maximumFlow;
}

// This method is used for finding the matchings
vector<vector<int>> 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)
{
this->_matchings.push_back({ nodeU, nodeV });
}
}
}

return this->_matchings;
}
}
1 change: 1 addition & 0 deletions SourceCodes/0003_Graph/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(0003GRAPH_SOURCES
0014_AllPairsShortestPathsJohnson.cc
0015_MaximumFlowFordFulkerson.cc
0016_MaximumFlowEdmondsKarp.cc
0017_MaximumBipartiteMatching.cc

)

Expand Down
35 changes: 35 additions & 0 deletions Tests/0003_Graph/0017_MaximumBipartiteMatchingTest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include<gtest/gtest.h>
#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<vector<int>> actualMatchings = graph.GetMatchings();

// Assert
ASSERT_EQ(expectedMaximumMatching, actualMaximumMatching);
ASSERT_EQ(expectedMatchings, unitTestHelper.SerializeVectorToString(actualMatchings));
}
}
1 change: 1 addition & 0 deletions Tests/0003_Graph/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(
0014_AllPairsShortestPathsJohnsonTest.cc
0015_MaximumFlowFordFulkersonTest.cc
0016_MaximumFlowEdmondsKarpTest.cc
0017_MaximumBipartiteMatchingTest.cc
)

target_link_libraries(
Expand Down