From 310420613ab996430ec968d56cfdb01bf342f642 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Mon, 2 Dec 2024 00:10:11 +0530 Subject: [PATCH 1/4] feature: eulerian path-cycle initial setup --- Headers/0003_Graph/0006_EulerianPathAndCircuit.h | 2 ++ SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc | 0 SourceCodes/0003_Graph/CMakeLists.txt | 1 + Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc | 0 Tests/0003_Graph/CMakeLists.txt | 1 + 5 files changed, 4 insertions(+) create mode 100644 Headers/0003_Graph/0006_EulerianPathAndCircuit.h create mode 100644 SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc create mode 100644 Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc diff --git a/Headers/0003_Graph/0006_EulerianPathAndCircuit.h b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc new file mode 100644 index 0000000..e69de29 diff --git a/SourceCodes/0003_Graph/CMakeLists.txt b/SourceCodes/0003_Graph/CMakeLists.txt index 39f75fb..4391ac4 100644 --- a/SourceCodes/0003_Graph/CMakeLists.txt +++ b/SourceCodes/0003_Graph/CMakeLists.txt @@ -5,6 +5,7 @@ set(0003GRAPH_SOURCES 0003_TopologicalSort.cc 0004_StronglyConnectedComponents.cc 0005_HamiltonianPathAndCycle.cc + 0006_EulerianPathAndCircuit.cc ) # Create a library target diff --git a/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc b/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc new file mode 100644 index 0000000..e69de29 diff --git a/Tests/0003_Graph/CMakeLists.txt b/Tests/0003_Graph/CMakeLists.txt index e58a3c7..3810012 100644 --- a/Tests/0003_Graph/CMakeLists.txt +++ b/Tests/0003_Graph/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable( 0003_TopologicalSortTest.cc 0004_StronglyConnectedComponentsTest.cc 0005_HamiltonianPathAndCycleTest.cc + 0006_EulerianPathAndCircuitTest.cc ) target_link_libraries( From 069b26bbc9e96a6f0bb510176456b1dd499bbd09 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Wed, 4 Dec 2024 01:10:34 +0530 Subject: [PATCH 2/4] feature: eulerian path logic added --- .../0003_Graph/0006_EulerianPathAndCircuit.h | 37 +++++ .../0003_Graph/0006_EulerianPathAndCircuit.cc | 152 ++++++++++++++++++ .../0006_EulerianPathAndCircuitTest.cc | 30 ++++ 3 files changed, 219 insertions(+) diff --git a/Headers/0003_Graph/0006_EulerianPathAndCircuit.h b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h index 3f59c93..0d269bd 100644 --- a/Headers/0003_Graph/0006_EulerianPathAndCircuit.h +++ b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h @@ -1,2 +1,39 @@ #pragma once +#include +#include +#include +using namespace std; + +namespace EulerianPathAndCircuit +{ + class Node + { + public: + int data; + int degree; + bool visited; + Node(int value); + }; + + class Graph + { + private: + bool _isEulerianPathPresent; + bool _isEulerianCircuitPresent; + map> _adjlist; + map _nodeMap; + vector _eulerianPath; + Node* MakeOrFindNode(int value); + void DepthFirstSearch(Node* node); + bool IsConnected(); + + public: + void PushUndirectedEdge(int valueU, int valueV); + void PushSingleNode(int valueU); + void FindEulerianPathAndCircuit(); + bool IsEulerianPathPresent(); + bool IsEulerianCircuitPresent(); + vector GetEulerianPath(); + }; +} \ No newline at end of file diff --git a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc index e69de29..56b9a50 100644 --- a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc +++ b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc @@ -0,0 +1,152 @@ +#include "../Headers/0003_Graph/0006_EulerianPathAndCircuit.h" +using namespace std; + +namespace EulerianPathAndCircuit +{ + Node::Node(int value) + { + this->data = value; + this->visited = false; + this->degree = 0; + } + + // Graph Private Member Methods + Node* Graph::MakeOrFindNode(int value) + { + Node* node = nullptr; + if (this->_nodeMap.find(value) == this->_nodeMap.end()) + { + node = new Node(value); + this->_nodeMap[value] = node; + } + else + { + node = this->_nodeMap[value]; + } + return node; + } + + // Graph Public Member Methods + void Graph::PushUndirectedEdge(int valueU, int valueV) + { + Node* nodeU = this->MakeOrFindNode(valueU); + Node* nodeV = this->MakeOrFindNode(valueV); + + this->_adjlist[nodeU].insert(nodeV); + nodeU->degree++; + this->_adjlist[nodeV].insert(nodeU); + nodeV->degree++; + } + + void Graph::DepthFirstSearch(Node* nodeU) + { + nodeU->visited = true; + this->_eulerianPath.push_back(nodeU->data); + for (auto& nodeV : this->_adjlist[nodeU]) + { + if (nodeV->visited == false) + { + this->DepthFirstSearch(nodeV); + } + } + } + + bool Graph::IsConnected() + { + // Step-1 : Make the visited property of all nodes as false. It is already done in constructor. + + // Step-2 : Find a node which do not have 0 degree. + Node* node = nullptr; + for (auto& iterator : this->_nodeMap) + { + if (iterator.second->degree != 0) + { + node = iterator.second; + break; + } + } + + // Step-3 : If node is null, it means G.E is null, so G is connected, else call DFS to traverse the graph G. + if (node == nullptr) + { + return true; + } + + this->DepthFirstSearch(node); + + // Step-4 : Checking if all the non-zero degree vertexes have been visited or not. + for (auto& iterator : this->_nodeMap) + { + if (iterator.second->visited == false && iterator.second->degree != 0) + { + return false; + } + } + return true; + } + + void Graph::PushSingleNode(int valueU) + { + Node* nodeU = this->MakeOrFindNode(valueU); + } + + void Graph::FindEulerianPathAndCircuit() + { + // If the graph is not connected then graph G is Not-Eulerian. + if (this->IsConnected() == false) + { + this->_isEulerianPathPresent = false; + this->_isEulerianCircuitPresent = false; + return; + } + + int oddDegreeVertexCount = 0; + for (auto& iterator : this->_nodeMap) + { + if (iterator.second->degree & 1) + { + oddDegreeVertexCount++; + } + } + + // Check-1 : When no vertex with odd degree is present, then graph G is Eulerian. + if (oddDegreeVertexCount == 0) + { + this->_isEulerianPathPresent = true; + this->_isEulerianCircuitPresent = true; + return; + } + + // Check-2 : When 2 vertex have odd degree, then graph G is Semi-Eulerian. + if (oddDegreeVertexCount == 2) + { + this->_isEulerianPathPresent = true; + this->_isEulerianCircuitPresent = false; + return; + } + + // Check-3 : When more than 2 vertexes have odd degree, then graph G is Not Eulerian. + if (oddDegreeVertexCount > 2) + { + this->_isEulerianPathPresent = false; + this->_isEulerianCircuitPresent = false; + return; + } + } + + bool Graph::IsEulerianPathPresent() + { + return this->_isEulerianPathPresent; + } + + bool Graph::IsEulerianCircuitPresent() + { + return this->_isEulerianCircuitPresent; + } + + // Not properly implemented + vector Graph::GetEulerianPath() + { + return this->_eulerianPath; + } +} \ No newline at end of file diff --git a/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc b/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc index e69de29..0675e04 100644 --- a/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc +++ b/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc @@ -0,0 +1,30 @@ +#include +#include "../Headers/0003_Graph/0006_EulerianPathAndCircuit.h" +#include "../0000_CommonUtilities/UnitTestHelper.h" + +namespace EulerianPathAndCircuit +{ + UnitTestHelper unitTestHelper; + + TEST(EulerianPathAndCycle, Test1) + { + Graph graph; + + graph.PushUndirectedEdge(1, 0); + graph.PushUndirectedEdge(0, 2); + graph.PushUndirectedEdge(2, 1); + graph.PushUndirectedEdge(0, 3); + graph.PushUndirectedEdge(3, 4); + graph.PushUndirectedEdge(4, 0); + + graph.FindEulerianPathAndCircuit(); + + bool isEulerianPathPresent = graph.IsEulerianPathPresent(); + bool isEulerianCircuitPresent = graph.IsEulerianCircuitPresent(); + + vector eulerianPath = graph.GetEulerianPath(); + + ASSERT_TRUE(isEulerianPathPresent); + ASSERT_TRUE(isEulerianCircuitPresent); + } +} \ No newline at end of file From acfd3fe9df0651d646635abc77fa14c638dba17a Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Tue, 10 Dec 2024 01:05:22 +0530 Subject: [PATCH 3/4] core: Hierholzer algorithm added --- .../0003_Graph/0006_EulerianPathAndCircuit.h | 10 +- .../0003_Graph/0006_EulerianPathAndCircuit.cc | 97 +++++++++++++++---- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/Headers/0003_Graph/0006_EulerianPathAndCircuit.h b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h index 0d269bd..eebea8c 100644 --- a/Headers/0003_Graph/0006_EulerianPathAndCircuit.h +++ b/Headers/0003_Graph/0006_EulerianPathAndCircuit.h @@ -2,7 +2,7 @@ #include #include -#include +#include using namespace std; namespace EulerianPathAndCircuit @@ -12,6 +12,8 @@ namespace EulerianPathAndCircuit public: int data; int degree; + int inDegree; + int outDegree; bool visited; Node(int value); }; @@ -21,19 +23,21 @@ namespace EulerianPathAndCircuit private: bool _isEulerianPathPresent; bool _isEulerianCircuitPresent; - map> _adjlist; + map> _adjlist; map _nodeMap; vector _eulerianPath; Node* MakeOrFindNode(int value); void DepthFirstSearch(Node* node); bool IsConnected(); + void EulerianPathHierholzerAlgorithm(Node* startingNode); public: void PushUndirectedEdge(int valueU, int valueV); + void PushDirectedEdge(int valueU, int valueV); void PushSingleNode(int valueU); void FindEulerianPathAndCircuit(); bool IsEulerianPathPresent(); bool IsEulerianCircuitPresent(); - vector GetEulerianPath(); + vector UndirectedGraphGetEulerianPath(); }; } \ No newline at end of file diff --git a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc index 56b9a50..aa8452c 100644 --- a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc +++ b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc @@ -1,4 +1,6 @@ #include "../Headers/0003_Graph/0006_EulerianPathAndCircuit.h" +#include +#include using namespace std; namespace EulerianPathAndCircuit @@ -6,8 +8,10 @@ namespace EulerianPathAndCircuit Node::Node(int value) { this->data = value; - this->visited = false; this->degree = 0; + this->inDegree = 0; + this->outDegree = 0; + this->visited = false; } // Graph Private Member Methods @@ -26,18 +30,6 @@ namespace EulerianPathAndCircuit return node; } - // Graph Public Member Methods - void Graph::PushUndirectedEdge(int valueU, int valueV) - { - Node* nodeU = this->MakeOrFindNode(valueU); - Node* nodeV = this->MakeOrFindNode(valueV); - - this->_adjlist[nodeU].insert(nodeV); - nodeU->degree++; - this->_adjlist[nodeV].insert(nodeU); - nodeV->degree++; - } - void Graph::DepthFirstSearch(Node* nodeU) { nodeU->visited = true; @@ -74,7 +66,7 @@ namespace EulerianPathAndCircuit this->DepthFirstSearch(node); - // Step-4 : Checking if all the non-zero degree vertexes have been visited or not. + // Step-4 : Checking if all the non-zero degree vertices have been visited or not. for (auto& iterator : this->_nodeMap) { if (iterator.second->visited == false && iterator.second->degree != 0) @@ -85,6 +77,50 @@ namespace EulerianPathAndCircuit return true; } + void Graph::EulerianPathHierholzerAlgorithm(Node* startingNode) + { + stack currentPath; + currentPath.push(startingNode); + while (!currentPath.empty()) + { + Node* currentNode = currentPath.top(); + if (!this->_adjlist[currentNode].empty()) + { + Node* nextNode = this->_adjlist[currentNode].front(); + this->_adjlist[currentNode].pop_front(); + this->_adjlist[nextNode].remove(currentNode); + currentPath.push(nextNode); + } + else + { + currentPath.pop(); + this->_eulerianPath.push_back(currentNode->data); + } + } + } + + // Graph Public Member Methods + void Graph::PushUndirectedEdge(int valueU, int valueV) + { + Node* nodeU = this->MakeOrFindNode(valueU); + Node* nodeV = this->MakeOrFindNode(valueV); + + this->_adjlist[nodeU].push_back(nodeV); + nodeU->degree++; + this->_adjlist[nodeV].push_back(nodeU); + nodeV->degree++; + } + + void Graph::PushDirectedEdge(int valueU, int valueV) + { + Node* nodeU = this->MakeOrFindNode(valueU); + Node* nodeV = this->MakeOrFindNode(valueV); + + this->_adjlist[nodeU].push_back(nodeV); + nodeU->outDegree++; + nodeV->inDegree++; + } + void Graph::PushSingleNode(int valueU) { Node* nodeU = this->MakeOrFindNode(valueU); @@ -117,7 +153,7 @@ namespace EulerianPathAndCircuit return; } - // Check-2 : When 2 vertex have odd degree, then graph G is Semi-Eulerian. + // Check-2 : When 2 vertices have odd degree, then graph G is Semi-Eulerian. if (oddDegreeVertexCount == 2) { this->_isEulerianPathPresent = true; @@ -125,7 +161,7 @@ namespace EulerianPathAndCircuit return; } - // Check-3 : When more than 2 vertexes have odd degree, then graph G is Not Eulerian. + // Check-3 : When more than 2 vertices have odd degree, then graph G is Not Eulerian. if (oddDegreeVertexCount > 2) { this->_isEulerianPathPresent = false; @@ -144,9 +180,34 @@ namespace EulerianPathAndCircuit return this->_isEulerianCircuitPresent; } - // Not properly implemented - vector Graph::GetEulerianPath() + vector Graph::UndirectedGraphGetEulerianPath() { + // Case-3 : When more than 2 vertices have odd degree, then the graph G is not Eulerian. + // No Eulerian Path is posible. + if (this->_isEulerianPathPresent == false) + { + return {}; + } + + // Now 2 cases remains. + // Case-2 : When 2 vertices have odd degree. Choose any one of them. + Node* node = nullptr; + for (auto& iterator : this->_nodeMap) + { + if (iterator.second->degree & 1) + { + node = iterator.second; + break; + } + } + + // Case-1 : When no vertex with odd degree is present. Choose any vertex as starting point. + if (node == nullptr) + { + node = this->_nodeMap[0]; + } + this->EulerianPathHierholzerAlgorithm(node); + reverse(this->_eulerianPath.begin(), this->_eulerianPath.end()); return this->_eulerianPath; } } \ No newline at end of file From 1002eb5f34bb09de9e9522ee5402dbecd1b3c962 Mon Sep 17 00:00:00 2001 From: Debashis Nandi Date: Wed, 11 Dec 2024 01:31:47 +0530 Subject: [PATCH 4/4] fix: Eulerian path code fixed --- SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc | 1 - Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc index aa8452c..87e1bbc 100644 --- a/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc +++ b/SourceCodes/0003_Graph/0006_EulerianPathAndCircuit.cc @@ -33,7 +33,6 @@ namespace EulerianPathAndCircuit void Graph::DepthFirstSearch(Node* nodeU) { nodeU->visited = true; - this->_eulerianPath.push_back(nodeU->data); for (auto& nodeV : this->_adjlist[nodeU]) { if (nodeV->visited == false) diff --git a/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc b/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc index 0675e04..0edc671 100644 --- a/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc +++ b/Tests/0003_Graph/0006_EulerianPathAndCircuitTest.cc @@ -22,9 +22,11 @@ namespace EulerianPathAndCircuit bool isEulerianPathPresent = graph.IsEulerianPathPresent(); bool isEulerianCircuitPresent = graph.IsEulerianCircuitPresent(); - vector eulerianPath = graph.GetEulerianPath(); + vector actualEulerianPath = graph.UndirectedGraphGetEulerianPath(); + vector expectedEulerianPath = { 0, 1, 2, 0, 3, 4, 0}; ASSERT_TRUE(isEulerianPathPresent); ASSERT_TRUE(isEulerianCircuitPresent); + EXPECT_EQ(expectedEulerianPath, actualEulerianPath); } } \ No newline at end of file