Skip to content

Commit

Permalink
DFS cycle detection (bobluppes#42)
Browse files Browse the repository at this point in the history
Co-authored-by: Bob Luppes <[email protected]>
  • Loading branch information
Hromz and bobluppes authored Jul 25, 2023
1 parent 8f4abf8 commit a6e198b
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ build/
# IDE
.vscode/
.cache/
.vs/
17 changes: 17 additions & 0 deletions include/graaflib/algorithm/cycle_detection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <graaflib/graph.h>

namespace graaf::algorithm {

/*
* @brief Traverses the graph and checks for cycles.
*
* @param graph The graph to traverse.
*/
template <typename V, typename E, graph_spec S>
[[nodiscard]] bool has_cycle(const graph<V, E, S> &graph);

} // namespace graaf::algorithm

#include "cycle_detection.tpp"
100 changes: 100 additions & 0 deletions include/graaflib/algorithm/cycle_detection.tpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#include <graaflib/types.h>

#include <algorithm>
#include <unordered_map>

namespace graaf::algorithm {

namespace detail {

enum class vertex_color { UNVISITED, VISITED, NO_CYCLE };

template <typename V, typename E, graph_spec S>
bool do_dfs_directed(const graph<V, E, S>& graph,
std::unordered_map<vertex_id_t, vertex_color>& colored_vertices,
vertex_id_t current) {
colored_vertices[current] = vertex_color::VISITED;

for (const auto& neighbour_vertex : graph.get_neighbors(current)) {
if (colored_vertices[neighbour_vertex] == vertex_color::UNVISITED) {
if (do_dfs_directed(graph, colored_vertices, neighbour_vertex))
return true;
}
else if (colored_vertices[neighbour_vertex] == vertex_color::VISITED) {
return true;
}
}

colored_vertices[current] = vertex_color::NO_CYCLE;
return false;
}

template<typename V, typename E, graph_spec S>
bool do_dfs_undirected(const graph<V,E,S> & graph,
std::unordered_map<vertex_id_t, bool> & visited_vertices,
std::unordered_map<vertex_id_t, vertex_id_t> & parent_vertices,
vertex_id_t parent_vertex, vertex_id_t current) {

visited_vertices[current] = true;

for (const auto& neighbour_vertex : graph.get_neighbors(current)) {
if (neighbour_vertex == parent_vertex)
continue;

if (visited_vertices[neighbour_vertex])
return true;

parent_vertices[neighbour_vertex] = parent_vertex;

if (do_dfs_undirected(graph, visited_vertices, parent_vertices, neighbour_vertex,
parent_vertices[neighbour_vertex])) {
return true;
}
}

return false;
}

} // namespace detail

template <typename V, typename E, graph_spec S>
bool has_cycle(const graph<V, E, S>& graph) {

if (graph.is_directed()) {
std::unordered_map<vertex_id_t, detail::vertex_color> colored_vertices{};

for (const auto& vertex : graph.get_vertices()) {
using enum detail::vertex_color;
if (colored_vertices[vertex.first] == UNVISITED &&
detail::do_dfs_directed(graph, colored_vertices, vertex.first)) {
return true;
}
}

return false;
}

if (graph.is_undirected()) {
// Number of vertices cannot be zero (in case if graph is empty)
if (graph.edge_count() >= graph.vertex_count() && graph.vertex_count() > 0) {
return true;
}

std::unordered_map<vertex_id_t, bool> visited_vertices{};
std::unordered_map<vertex_id_t, vertex_id_t> parent_vertices{};

for (const auto& vertex : graph.get_vertices()) {
if (!visited_vertices.contains(vertex.first) &&
detail::do_dfs_undirected(graph, visited_vertices, parent_vertices,
vertex.first, parent_vertices[vertex.first])) {
return true;
}
}
}

return false;
}

} // namespace graaf::algorithm
172 changes: 172 additions & 0 deletions test/graaflib/algorithm/cycle_detection_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#include <graaflib/algorithm/cycle_detection.h>
#include <graaflib/directed_graph.h>
#include <graaflib/types.h>
#include <graaflib/undirected_graph.h>
#include <gtest/gtest.h>

#include <unordered_map>
#include <unordered_set>
#include <utility>

namespace graaf::algorithm {

namespace {

template <typename T>
struct GraphCycleTest : public testing::Test {
using graph_t = T;
};

using graph_types =
testing::Types<directed_graph<int, int>, undirected_graph<int, int>>;
TYPED_TEST_SUITE(GraphCycleTest, graph_types);

} // namespace

TYPED_TEST(GraphCycleTest, DirectedGraphWithCycle) {
// GIVEN
directed_graph<int, int> graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);
graph.add_edge(vertex_3, vertex_1, 300);

// checking if graph contains cycles
bool cycle = has_cycle(graph);
ASSERT_TRUE(cycle);
}

TYPED_TEST(GraphCycleTest, DirectedGraphWithCycleMiddle) {
// GIVEN
directed_graph<int, int> graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);
graph.add_edge(vertex_2, vertex_1, 300);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_TRUE(cycle);
}

TYPED_TEST(GraphCycleTest, DirectedGraphWithoutCycle) {
// GIVEN
directed_graph<int, int> graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_FALSE(cycle);
}

TYPED_TEST(GraphCycleTest, UndirectedGraphWithoutCycle) {
// GIVEN
undirected_graph<int, int> graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_FALSE(cycle);
}

TYPED_TEST(GraphCycleTest, UndirectedGraphWithCycle) {
// GIVEN
undirected_graph<int, int> graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);
graph.add_edge(vertex_3, vertex_1, 400);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_TRUE(cycle);
}

TYPED_TEST(GraphCycleTest, EmptyGraphs) {
// GIVEN
directed_graph<int, int> directed_graph{};
undirected_graph<int, int> undirected_graph{};

// checking if graph contains cycles
bool cycle = has_cycle(directed_graph);
ASSERT_FALSE(cycle);

// checking if graph contains cycles
cycle = has_cycle(undirected_graph);
ASSERT_FALSE(cycle);
}

TYPED_TEST(GraphCycleTest, DefaultGraphWithCycle) {
// GIVEN
using graph_t = typename TestFixture::graph_t;
graph_t graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);
graph.add_edge(vertex_3, vertex_1, 400);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_TRUE(cycle);
}

TYPED_TEST(GraphCycleTest, DefaultGraphWithoutCycle) {
// GIVEN
using graph_t = typename TestFixture::graph_t;
graph_t graph{};

// graph vertices
const auto vertex_1{graph.add_vertex(10)};
const auto vertex_2{graph.add_vertex(20)};
const auto vertex_3{graph.add_vertex(30)};

// adding edges to our graph
graph.add_edge(vertex_1, vertex_2, 100);
graph.add_edge(vertex_2, vertex_3, 300);

// checking if graph contains cycle
bool cycle = has_cycle(graph);
ASSERT_FALSE(cycle);
}

} // namespace graaf::algorithm

0 comments on commit a6e198b

Please sign in to comment.