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.
feat: add topological sorting (DFS-based) (bobluppes#93)
- Loading branch information
Showing
5 changed files
with
337 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
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,21 @@ | ||
--- | ||
sidebar_position: 1 | ||
--- | ||
|
||
# Topological sort algorithm | ||
Topological sort algorithm processing DAG(directed acyclic graph) using DFS traversal. | ||
Each vertex is visited only after all its dependencies are visited. | ||
The runtime of the algorithm is `O(|V|+|E|)` and the memory consumption is `O(|V|)`. | ||
|
||
[wikipedia](https://en.wikipedia.org/wiki/Topological_sorting) | ||
|
||
## Syntax | ||
|
||
```cpp | ||
template <typename V, typename E> | ||
[[nodiscard]] std::optional<std::vector<vertex_id_t>> topological_sort( | ||
const graph<V, E, graph_type::DIRECTED>& graph); | ||
``` | ||
- **graph** The directed graph to traverse. | ||
- **return** Vector of vertices sorted in topological order. If the graph contains cycles, it returns std::nullopt. |
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,22 @@ | ||
#pragma once | ||
|
||
#include <graaflib/graph.h> | ||
|
||
#include <optional> | ||
|
||
namespace graaf::algorithm { | ||
/** | ||
* @brief Calculates order of vertices in topological order | ||
* using DFS traversal | ||
* | ||
* @tparam V The vertex type of the graph. | ||
* @tparam E The edge type of the graph. | ||
* @param graph The input graph. | ||
* @return Vector of vertices sorted in topological order | ||
*/ | ||
template <typename V, typename E> | ||
[[nodiscard]] std::optional<std::vector<vertex_id_t>> topological_sort( | ||
const graph<V, E, graph_type::DIRECTED>& graph); | ||
|
||
} // namespace graaf::algorithm | ||
#include "topological_sorting.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,58 @@ | ||
#pragma once | ||
|
||
#include <graaflib/algorithm/cycle_detection.h> | ||
#include <graaflib/algorithm/topological_sorting.h> | ||
|
||
#include <vector> | ||
#include <optional> | ||
#include <unordered_set> | ||
|
||
namespace graaf::algorithm { | ||
|
||
namespace detail { | ||
|
||
// DFS topological sort | ||
template <typename V, typename E> | ||
void do_dfs_topological_sort( | ||
const graph<V, E, graph_type::DIRECTED>& graph, | ||
vertex_id_t start_vertex, | ||
std::unordered_set<vertex_id_t>& processed_vertices, | ||
std::vector<vertex_id_t>& sorted_vertices) { | ||
|
||
processed_vertices.insert(start_vertex); | ||
for (const auto& next_vertex : graph.get_neighbors(start_vertex)) { | ||
if (!processed_vertices.contains(next_vertex)) { | ||
do_dfs_topological_sort(graph, next_vertex, processed_vertices, | ||
sorted_vertices); | ||
} | ||
} | ||
|
||
sorted_vertices.push_back(start_vertex); | ||
} | ||
|
||
}; // namespace detail | ||
|
||
template <typename V, typename E> | ||
std::optional<std::vector<vertex_id_t>> topological_sort( | ||
const graph<V, E, graph_type::DIRECTED>& graph) { | ||
|
||
// Graph should be acyclic | ||
if (dfs_cycle_detection(graph)) { | ||
return std::nullopt; | ||
} | ||
|
||
std::vector<vertex_id_t> sorted_vertices{}; | ||
sorted_vertices.reserve(graph.vertex_count()); | ||
std::unordered_set<vertex_id_t> processed_vertices{}; | ||
|
||
for (const auto& [vertex_id, _] : graph.get_vertices()) { | ||
if (!processed_vertices.contains(vertex_id)) { | ||
detail::do_dfs_topological_sort(graph, vertex_id, processed_vertices, sorted_vertices); | ||
} | ||
} | ||
|
||
std::reverse(sorted_vertices.begin(), sorted_vertices.end()); | ||
return sorted_vertices; | ||
} | ||
|
||
}; // 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,234 @@ | ||
#include <fmt/core.h> | ||
#include <graaflib/algorithm/topological_sorting.h> | ||
#include <graaflib/graph.h> | ||
#include <graaflib/types.h> | ||
#include <gtest/gtest.h> | ||
|
||
namespace graaf::algorithm { | ||
namespace { | ||
template <typename T> | ||
struct TypedTopologicalSort : public testing::Test { | ||
using graph_t = T; | ||
}; | ||
|
||
using graph_types = testing::Types<directed_graph<int, int>>; | ||
TYPED_TEST_SUITE(TypedTopologicalSort, graph_types); | ||
|
||
}; // namespace | ||
|
||
TYPED_TEST(TypedTopologicalSort, ShortGraph) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
|
||
graph.add_edge(vertex_1, vertex_2, 25); | ||
graph.add_edge(vertex_2, vertex_3, 35); | ||
graph.add_edge(vertex_3, vertex_4, 45); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
std::vector<vertex_id_t> expected_vertices{vertex_1, vertex_2, vertex_3, | ||
vertex_4}; | ||
|
||
// THEN | ||
ASSERT_EQ(expected_vertices, sorted_vertices); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, RhombusShapeGraph) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
|
||
graph.add_edge(vertex_1, vertex_2, 25); | ||
graph.add_edge(vertex_1, vertex_3, 35); | ||
graph.add_edge(vertex_3, vertex_4, 45); | ||
graph.add_edge(vertex_2, vertex_4, 55); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
std::vector<vertex_id_t> expected_vertices_1{vertex_1, vertex_2, vertex_3, | ||
vertex_4}; | ||
std::vector<vertex_id_t> expected_vertices_2{vertex_1, vertex_3, vertex_2, | ||
vertex_4}; | ||
|
||
// THEN | ||
ASSERT_TRUE(expected_vertices_1 == sorted_vertices || | ||
expected_vertices_2 == sorted_vertices); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, CycleGraph) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
|
||
graph.add_edge(vertex_1, vertex_2, 25); | ||
graph.add_edge(vertex_2, vertex_3, 35); | ||
graph.add_edge(vertex_3, vertex_4, 45); | ||
graph.add_edge(vertex_4, vertex_1, 55); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
const auto expected_vertices = std::nullopt; | ||
|
||
// THEN | ||
ASSERT_EQ(expected_vertices, sorted_vertices); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, GraphWithParallelEdge) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
|
||
graph.add_edge(vertex_1, vertex_2, 25); | ||
graph.add_edge(vertex_3, vertex_4, 35); | ||
graph.add_edge(vertex_4, vertex_1, 45); | ||
graph.add_edge(vertex_1, vertex_4, 55); | ||
|
||
// WHEN | ||
auto sorted_vertices = topological_sort(graph); | ||
const auto expected_vertices = std::nullopt; | ||
|
||
// THEN | ||
ASSERT_EQ(expected_vertices, sorted_vertices); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, SelfLoop) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
|
||
graph.add_edge(vertex_1, vertex_1, -1); | ||
graph.add_edge(vertex_2, vertex_2, -1); | ||
graph.add_edge(vertex_1, vertex_2, 15); | ||
graph.add_edge(vertex_3, vertex_4, 25); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
const auto expected_vertices = std::nullopt; | ||
|
||
// THEN | ||
ASSERT_EQ(expected_vertices, sorted_vertices); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, SimpleGraph) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
const auto vertex_5{graph.add_vertex(50)}; | ||
const auto vertex_6{graph.add_vertex(60)}; | ||
const auto vertex_7{graph.add_vertex(70)}; | ||
|
||
graph.add_edge(vertex_1, vertex_5, 1); | ||
graph.add_edge(vertex_5, vertex_3, 2); | ||
graph.add_edge(vertex_3, vertex_7, 3); | ||
graph.add_edge(vertex_1, vertex_4, 4); | ||
graph.add_edge(vertex_1, vertex_2, 5); | ||
graph.add_edge(vertex_4, vertex_2, 6); | ||
graph.add_edge(vertex_2, vertex_6, 7); | ||
graph.add_edge(vertex_6, vertex_3, 8); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
std::vector<vertex_id_t> expected_vertices_1{ | ||
vertex_1, vertex_4, vertex_2, vertex_6, vertex_5, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_2{ | ||
vertex_1, vertex_2, vertex_4, vertex_6, vertex_5, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_3{ | ||
vertex_1, vertex_2, vertex_4, vertex_5, vertex_6, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_4{ | ||
vertex_1, vertex_5, vertex_4, vertex_2, vertex_6, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_5{ | ||
vertex_1, vertex_5, vertex_2, vertex_4, vertex_6, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_6{ | ||
vertex_1, vertex_2, vertex_5, vertex_4, vertex_6, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_7{ | ||
vertex_1, vertex_4, vertex_2, vertex_5, vertex_6, vertex_3, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_8{ | ||
vertex_1, vertex_4, vertex_5, vertex_2, vertex_6, vertex_3, vertex_7}; | ||
|
||
// THEN | ||
ASSERT_TRUE((expected_vertices_1 == sorted_vertices) || | ||
(expected_vertices_2 == sorted_vertices) || | ||
(expected_vertices_3 == sorted_vertices) || | ||
(expected_vertices_4 == sorted_vertices) || | ||
(expected_vertices_5 == sorted_vertices) || | ||
(expected_vertices_6 == sorted_vertices) || | ||
(expected_vertices_7 == sorted_vertices) || | ||
(expected_vertices_8 == sorted_vertices)); | ||
} | ||
|
||
TYPED_TEST(TypedTopologicalSort, SixSortResults) { | ||
// GIVEN | ||
using graph_t = typename TestFixture::graph_t; | ||
graph_t graph{}; | ||
|
||
const auto vertex_1{graph.add_vertex(10)}; | ||
const auto vertex_2{graph.add_vertex(20)}; | ||
const auto vertex_3{graph.add_vertex(30)}; | ||
const auto vertex_4{graph.add_vertex(40)}; | ||
const auto vertex_5{graph.add_vertex(50)}; | ||
const auto vertex_6{graph.add_vertex(60)}; | ||
const auto vertex_7{graph.add_vertex(70)}; | ||
|
||
graph.add_edge(vertex_1, vertex_2, 1); | ||
graph.add_edge(vertex_2, vertex_3, 2); | ||
graph.add_edge(vertex_1, vertex_4, 3); | ||
graph.add_edge(vertex_4, vertex_5, 4); | ||
graph.add_edge(vertex_1, vertex_6, 5); | ||
graph.add_edge(vertex_6, vertex_7, 6); | ||
|
||
// WHEN; | ||
auto sorted_vertices = topological_sort(graph); | ||
std::vector<vertex_id_t> expected_vertices_1{ | ||
vertex_1, vertex_2, vertex_3, vertex_4, vertex_5, vertex_6, vertex_7}; | ||
std::vector<vertex_id_t> expected_vertices_2{ | ||
vertex_1, vertex_2, vertex_3, vertex_6, vertex_7, vertex_4, vertex_5}; | ||
std::vector<vertex_id_t> expected_vertices_3{ | ||
vertex_1, vertex_6, vertex_7, vertex_2, vertex_3, vertex_4, vertex_5}; | ||
std::vector<vertex_id_t> expected_vertices_4{ | ||
vertex_1, vertex_6, vertex_7, vertex_4, vertex_5, vertex_2, vertex_3}; | ||
std::vector<vertex_id_t> expected_vertices_5{ | ||
vertex_1, vertex_4, vertex_5, vertex_6, vertex_7, vertex_2, vertex_3}; | ||
std::vector<vertex_id_t> expected_vertices_6{ | ||
vertex_1, vertex_4, vertex_5, vertex_2, vertex_3, vertex_6, vertex_7}; | ||
|
||
// THEN | ||
ASSERT_TRUE((expected_vertices_1 == sorted_vertices) || | ||
(expected_vertices_2 == sorted_vertices) || | ||
(expected_vertices_3 == sorted_vertices) || | ||
(expected_vertices_4 == sorted_vertices) || | ||
(expected_vertices_4 == sorted_vertices) || | ||
(expected_vertices_6 == sorted_vertices)); | ||
} | ||
|
||
}; // namespace graaf::algorithm |