forked from bobluppes/graaf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Bob Luppes <[email protected]>
- Loading branch information
Showing
4 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ build/ | |
# IDE | ||
.vscode/ | ||
.cache/ | ||
.vs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |