feat: Add BFS and DFS algorithms to check for cycle in a directed graph (#816)

* feat: Add BFS and DFS algorithms to check for cycle in a directed graph

* Remove const references for input of simple types

Reason: overhead on access

* fix bad code

sorry for force push

* Use pointer instead of the non-const reference

because apparently google says so.

* Remove a useless and possibly bad Graph constuctor overload

* Explicitely specify type of vector during graph instantiation

* Minor documentation fixes.

* Fix bad code

why did I even do this lol.

* Some more fixes to the code

* Fix a comment.

* Use map instead of unordered_map for better performance when using map.find() function and make code compatible with C++11.

* Remove copyright line.
This commit is contained in:
Anmol Mittal
2020-07-19 21:36:29 +05:30
committed by GitHub
parent 67ec2aa982
commit 0b27a9e631

View File

@@ -1,21 +1,19 @@
/**
* Copyright 2020
* @file cycle_check_directed graph.cpp
*
* @brief BFS and DFS algorithms to check for cycle in a directed graph.
*
* @author Anmol3299
* contact: mittalanmol22@gmail.com
* @author [Anmol3299](mailto:mittalanmol22@gmail.com)
*
*/
#include <iostream> // for std::cout
#include <queue> // for std::queue
#include <stdexcept> // for throwing errors
#include <type_traits> // for std::remove_reference_t
#include <unordered_map> // for std::unordered_map
#include <utility> // for std::move
#include <vector> // for std::vector
#include <iostream> // for std::cout
#include <map> // for std::map
#include <queue> // for std::queue
#include <stdexcept> // for throwing errors
#include <type_traits> // for std::remove_reference
#include <utility> // for std::move
#include <vector> // for std::vector
/**
* Implementation of non-weighted directed edge of a graph.
@@ -43,7 +41,7 @@ struct Edge {
: src(source), dest(destination) {}
};
using AdjList = std::unordered_map<unsigned int, std::vector<unsigned int>>;
using AdjList = std::map<unsigned int, std::vector<unsigned int>>;
/**
* Implementation of graph class.
@@ -76,7 +74,7 @@ class Graph {
* @param adjList is the adjacency list representation of graph.
*/
Graph(unsigned int vertices, AdjList&& adjList)
: m_vertices(std::move(vertices)), m_adjList(std::move(adjList)) {}
: m_vertices(vertices), m_adjList(std::move(adjList)) {}
/** Create a graph from vertices and a set of edges.
*
@@ -102,16 +100,14 @@ class Graph {
*
* @return const reference to the adjacency list
*/
std::remove_reference_t<AdjList> const& getAdjList() const {
std::remove_reference<AdjList>::type const& getAdjList() const {
return m_adjList;
}
/**
* @return number of vertices in the graph.
*/
std::remove_reference_t<unsigned int> const& getVertices() const {
return m_vertices;
}
unsigned int getVertices() const { return m_vertices; }
/** Add vertices in the graph.
*
@@ -150,6 +146,15 @@ class Graph {
AdjList m_adjList;
};
/**
* Check if a directed graph has a cycle or not.
*
* This class provides 2 methods to check for cycle in a directed graph:
* isCyclicDFS & isCyclicBFS.
*
* - isCyclicDFS uses DFS traversal method to check for cycle in a graph.
* - isCyclidBFS used BFS traversal method to check for cycle in a graph.
*/
class CycleCheck {
private:
enum nodeStates : uint8_t { not_visited = 0, in_stack, visited };
@@ -170,12 +175,13 @@ class CycleCheck {
// If the node has children, then recursively visit all children of the
// node.
if (auto const& it = adjList.find(node); it != adjList.end()) {
auto const it = adjList.find(node);
if (it != adjList.end()) {
for (auto child : it->second) {
// If state of child node is "not_visited", evaluate that child
// for presence of cycle.
if (auto state_of_child = (*state)[child];
state_of_child == not_visited) {
auto state_of_child = (*state)[child];
if (state_of_child == not_visited) {
if (isCyclicDFSHelper(adjList, state, child)) {
return true;
}
@@ -204,6 +210,8 @@ class CycleCheck {
* @return true if a cycle is detected, else false.
*/
static bool isCyclicDFS(Graph const& graph) {
auto vertices = graph.getVertices();
/** State of the node.
*
* It is a vector of "nodeStates" which represents the state node is in.
@@ -211,10 +219,10 @@ class CycleCheck {
*
* Initially, all nodes are in "not_visited" state.
*/
std::vector<nodeStates> state(graph.getVertices(), not_visited);
std::vector<nodeStates> state(vertices, not_visited);
// Start visiting each node.
for (auto node = 0; node < graph.getVertices(); node++) {
for (unsigned int node = 0; node < vertices; node++) {
// If a node is not visited, only then check for presence of cycle.
// There is no need to check for presence of cycle for a visited
// node as it has already been checked for presence of cycle.
@@ -239,18 +247,20 @@ class CycleCheck {
* @return true if a cycle is detected, else false.
*/
static bool isCyclicBFS(Graph const& graph) {
AdjList graphAjdList = graph.getAdjList();
auto graphAjdList = graph.getAdjList();
auto vertices = graph.getVertices();
std::vector<unsigned int> indegree(graph.getVertices(), 0);
std::vector<unsigned int> indegree(vertices, 0);
// Calculate the indegree i.e. the number of incident edges to the node.
for (auto const& [parent, children] : graphAjdList) {
for (auto const& list : graphAjdList) {
auto children = list.second;
for (auto const& child : children) {
indegree[child]++;
}
}
std::queue<unsigned int> can_be_solved;
for (auto node = 0; node < graph.getVertices(); node++) {
for (unsigned int node = 0; node < vertices; node++) {
// If a node doesn't have any input edges, then that node will
// definately not result in a cycle and can be visited safely.
if (!indegree[node]) {
@@ -259,17 +269,18 @@ class CycleCheck {
}
// Vertices that need to be traversed.
auto remain = graph.getVertices();
auto remain = vertices;
// While there are safe nodes that we can visit.
while (!can_be_solved.empty()) {
auto front = can_be_solved.front();
auto solved = can_be_solved.front();
// Visit the node.
can_be_solved.pop();
// Decrease number of nodes that need to be traversed.
remain--;
// Visit all the children of the visited node.
if (auto it = graphAjdList.find(front); it != graphAjdList.end()) {
auto it = graphAjdList.find(solved);
if (it != graphAjdList.end()) {
for (auto child : it->second) {
// Check if we can visited the node safely.
if (--indegree[child] == 0) {