From c35e170f08609da7d347f23f954ecb50579a755d Mon Sep 17 00:00:00 2001 From: toadkarter Date: Sun, 8 Oct 2023 20:19:21 +0100 Subject: [PATCH 01/10] Adding file structure for Kosaraju --- .../strongly_connected_components/kosaraju.h | 17 +++++++++++++++++ .../strongly_connected_components/kosaraju.tpp | 14 ++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 include/graaflib/algorithm/strongly_connected_components/kosaraju.h create mode 100644 include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.h b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h new file mode 100644 index 00000000..921dc2ff --- /dev/null +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include + +namespace graaf::algorithm { + +template +std::vector> kosarajus_strongly_connected_components( + const graph& graph +); + +} // namespace graaf::algorithm + +#include "kosaraju.tpp" \ No newline at end of file diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp new file mode 100644 index 00000000..555f878d --- /dev/null +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace graaf::algorithm { + +template +std::vector> kosarajus_strongly_connected_components( + const graph& graph +) { +} + + +} // namespace graaf::algorithm From e108b571e477279aa934cb83e78aa34408f90935 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Mon, 9 Oct 2023 20:53:45 +0100 Subject: [PATCH 02/10] Added Kosaraju's algorithm and corresponding test. --- .../strongly_connected_components/kosaraju.h | 18 +++++- .../kosaraju.tpp | 64 ++++++++++++++++++- .../kosaraju_test.cpp | 53 +++++++++++++++ 3 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.h b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h index 921dc2ff..dbed4dc0 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.h +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h @@ -7,10 +7,24 @@ namespace graaf::algorithm { +/** + * @brief Finds strongly connected components in a directed graph using + * Kosaraju's algorithm. + * + * Kosaraju's algorithm identifies strongly connected components (SCCs) in a + * directed graph. The algorithm uses two depth-first searches to discover these + * components. + * + * @tparam V The vertex type of the graph. + * @tparam E The edge type of the graph. + * @param graph The input directed graph. + * + * @return A std::vector of vectors, each of which contains the vertex IDs + * forming a strongly connected component. + */ template std::vector> kosarajus_strongly_connected_components( - const graph& graph -); + const directed_graph& graph); } // namespace graaf::algorithm diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index 555f878d..a48d6489 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -1,14 +1,72 @@ #pragma once +#include #include +#include + +#include +#include namespace graaf::algorithm { template std::vector> kosarajus_strongly_connected_components( - const graph& graph -) { -} + const directed_graph& graph) { + std::vector> sccs; + + if (graph.get_vertices().size() == 0) { + return sccs; + } + + std::stack stack; + std::unordered_set seen_vertices; + + std::function visit_vertex; + visit_vertex = [&](vertex_id_t vertex) { + if (!seen_vertices.contains(vertex)) { + stack.push(vertex); + seen_vertices.insert(vertex); + + for (const auto neighbour : graph.get_neighbors(vertex)) { + visit_vertex(neighbour); + } + } + }; + + for (const auto& [vertex_id, vertex] : graph.get_vertices()) { + if (!seen_vertices.contains(vertex_id)) { + visit_vertex(vertex_id); + } + } + seen_vertices.clear(); + + directed_graph transposed_graph = get_transposed_graph(graph); + std::vector scc; + + std::function strong_connect; + strong_connect = [&](vertex_id_t vertex) { + if (!seen_vertices.contains(vertex)) { + seen_vertices.insert(vertex); + scc.push_back(vertex); + + for (const auto neighbour : transposed_graph.get_neighbors(vertex)) { + strong_connect(neighbour); + } + } + }; + + while (!stack.empty()) { + if (!seen_vertices.contains(stack.top())) { + scc.clear(); + strong_connect(stack.top()); + sccs.push_back(scc); + } + + stack.pop(); + } + + return sccs; +} } // namespace graaf::algorithm diff --git a/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp new file mode 100644 index 00000000..01d393aa --- /dev/null +++ b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp @@ -0,0 +1,53 @@ +#include +#include + +namespace graaf::algorithm { + +TEST(KosarajuTest, EqualOutputSCCS) { + using sccs_t = std::vector>; + + // GIVEN + directed_graph graph{}; + const auto vertex_id_1 = graph.add_vertex(1, 1); + const auto vertex_id_2 = graph.add_vertex(2, 2); + const auto vertex_id_3 = graph.add_vertex(3, 3); + const auto vertex_id_4 = graph.add_vertex(4, 4); + const auto vertex_id_5 = graph.add_vertex(5, 5); + const auto vertex_id_6 = graph.add_vertex(6, 6); + const auto vertex_id_7 = graph.add_vertex(7, 7); + const auto vertex_id_8 = graph.add_vertex(8, 8); + + graph.add_edge(vertex_id_1, vertex_id_2, 1); + graph.add_edge(vertex_id_2, vertex_id_3, 1); + graph.add_edge(vertex_id_3, vertex_id_4, 1); + graph.add_edge(vertex_id_3, vertex_id_1, 1); + graph.add_edge(vertex_id_4, vertex_id_5, 1); + graph.add_edge(vertex_id_5, vertex_id_8, 1); + graph.add_edge(vertex_id_5, vertex_id_6, 1); + graph.add_edge(vertex_id_6, vertex_id_7, 1); + graph.add_edge(vertex_id_7, vertex_id_8, 1); + graph.add_edge(vertex_id_7, vertex_id_5, 1); + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{1, 2, 3}, {4}, {5, 6, 7}, {8}}; + + // THEN + ASSERT_EQ(sccs.size(), expected_sccs.size()); + + std::sort(sccs.begin(), sccs.end()); + std::sort(expected_sccs.begin(), expected_sccs.end()); + + for (int i = 0; i < sccs.size(); i++) { + ASSERT_EQ(sccs[i].size(), expected_sccs[i].size()); + + std::sort(sccs[i].begin(), sccs[i].end()); + std::sort(expected_sccs[i].begin(), expected_sccs[i].end()); + + ASSERT_EQ(sccs[i], expected_sccs[i]); + } + + ASSERT_EQ(sccs, expected_sccs); +} + +} // namespace graaf::algorithm \ No newline at end of file From 03b29d831b68fa239c839f5ec18f5bb95ee8f7e3 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Mon, 9 Oct 2023 21:11:21 +0100 Subject: [PATCH 03/10] Updates documentation with Kosaraju's algorithm. --- .../kosarajus.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/docs/algorithms/strongly-connected-components/kosarajus.md diff --git a/docs/docs/algorithms/strongly-connected-components/kosarajus.md b/docs/docs/algorithms/strongly-connected-components/kosarajus.md new file mode 100644 index 00000000..b83796b7 --- /dev/null +++ b/docs/docs/algorithms/strongly-connected-components/kosarajus.md @@ -0,0 +1,20 @@ +# Kosaraju's Strongly Connected Components + +Kosaraju's algorithm computes the Strongly Connected Components (SCCs) of a directed graph. An SCC is a subset of vertices +in the graph for which every vertex is reachable from every other vertex in the subset, i.e. there exists a path between +all pairs of vertices for the subset of vertices. + +Kosaraju's algorithm runs in `O(|V| + |E|)` for directed graphs, where `|V|` the number of vertices and `|E|` is the +number of edges in the graph. So it runs in linear time. + +[wikipedia](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm) + +## Syntax + +```cpp +template +std::vector> kosarajus_strongly_connected_components(const directed_graph& graph); +``` + +- **graph** The graph for which to compute SCCs. +- **return** A vector of vectors representing SCCs. \ No newline at end of file From f3cf196c8bbae7826efc6cd26dca7c89142a45d9 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Mon, 9 Oct 2023 21:12:52 +0100 Subject: [PATCH 04/10] Updates ReadMe with Kosaraju's algorithm. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bf98c12a..9a67674b 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ take a look at the [docs](https://bobluppes.github.io/graaf/docs/algorithms/intr - [Floyd-Warshall Algorithm](https://bobluppes.github.io/graaf/docs/algorithms/shortest-path/floyd-warshall) 5. [**Strongly Connected Components Algorithms**](https://bobluppes.github.io/graaf/docs/category/strongly-connected-component-algorithms): - [Tarjan's Strongly Connected Components](https://bobluppes.github.io/graaf/docs/algorithms/strongly-connected-components/tarjan) + - [Kosaraju's Strongly Connected Components](https://bobluppes.github.io/graaf/docs/algorithms/strongly-connected-components/kosarajus) 6. [**Topological Sorting Algorithms**](https://bobluppes.github.io/graaf/docs/algorithms/topological-sort): 7. [**Traversal Algorithms**](https://bobluppes.github.io/graaf/docs/category/traversal-algorithms): - [Breadth-First Search (BFS)](https://bobluppes.github.io/graaf/docs/algorithms/traversal/breadth-first-search) From bcdf6d6625a26eae7e3fb00fe8ec6466fb330919 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 20:22:57 +0100 Subject: [PATCH 05/10] Adding brace initialization to various data structures in Kosaraju and Tarjan --- .../algorithm/strongly_connected_components/kosaraju.tpp | 8 ++++---- .../algorithm/strongly_connected_components/tarjan.tpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index a48d6489..632716c1 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -12,14 +12,14 @@ namespace graaf::algorithm { template std::vector> kosarajus_strongly_connected_components( const directed_graph& graph) { - std::vector> sccs; + std::vector> sccs{}; if (graph.get_vertices().size() == 0) { return sccs; } - std::stack stack; - std::unordered_set seen_vertices; + std::stack stack{}; + std::unordered_set seen_vertices{}; std::function visit_vertex; visit_vertex = [&](vertex_id_t vertex) { @@ -42,7 +42,7 @@ std::vector> kosarajus_strongly_connected_components( seen_vertices.clear(); directed_graph transposed_graph = get_transposed_graph(graph); - std::vector scc; + std::vector scc{}; std::function strong_connect; strong_connect = [&](vertex_id_t vertex) { diff --git a/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp b/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp index 50358f51..597e51a4 100644 --- a/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp @@ -14,15 +14,15 @@ template tarjans_strongly_connected_components( const graph& graph) { // Vector to store strongly connected components - std::vector> sccs; + std::vector> sccs{}; // Stack to hold vertices during traversal std::stack stack; // Maps to keep track of indices, low-link values, and stack membership - std::unordered_map indices; - std::unordered_map low_links; - std::unordered_map on_stack; + std::unordered_map indices{}; + std::unordered_map low_links{}; + std::unordered_map on_stack{}; // Counter for indexing vertices size_t index_counter = 0; From 09a94ed707961df2617083d4913affb4188aa34a Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 20:48:23 +0100 Subject: [PATCH 06/10] Extracting helper functions into anonymous namespace in Kosaraju. --- .../kosaraju.tpp | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index 632716c1..a1c29fa5 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -9,6 +9,41 @@ namespace graaf::algorithm { +namespace { + +template +void do_visit_vertex(const vertex_id_t vertex_id, + const directed_graph& graph, + std::stack& stack, + std::unordered_set& seen_vertices) { + if (!seen_vertices.contains(vertex_id)) { + stack.push(vertex_id); + seen_vertices.insert(vertex_id); + + for (const auto neighbour : graph.get_neighbors(vertex_id)) { + do_visit_vertex(neighbour, graph, stack, seen_vertices); + } + } +} + +template +void make_strongly_connected_component_from_vertex( + const vertex_id_t vertex, const directed_graph& transposed_graph, + std::vector& scc, + std::unordered_set& seen_vertices) { + if (!seen_vertices.contains(vertex)) { + seen_vertices.insert(vertex); + scc.push_back(vertex); + + for (const auto neighbour : transposed_graph.get_neighbors(vertex)) { + make_strongly_connected_component_from_vertex(neighbour, transposed_graph, + scc, seen_vertices); + } + } +} + +} // namespace + template std::vector> kosarajus_strongly_connected_components( const directed_graph& graph) { @@ -21,21 +56,9 @@ std::vector> kosarajus_strongly_connected_components( std::stack stack{}; std::unordered_set seen_vertices{}; - std::function visit_vertex; - visit_vertex = [&](vertex_id_t vertex) { - if (!seen_vertices.contains(vertex)) { - stack.push(vertex); - seen_vertices.insert(vertex); - - for (const auto neighbour : graph.get_neighbors(vertex)) { - visit_vertex(neighbour); - } - } - }; - for (const auto& [vertex_id, vertex] : graph.get_vertices()) { if (!seen_vertices.contains(vertex_id)) { - visit_vertex(vertex_id); + do_visit_vertex(vertex_id, graph, stack, seen_vertices); } } @@ -44,22 +67,11 @@ std::vector> kosarajus_strongly_connected_components( directed_graph transposed_graph = get_transposed_graph(graph); std::vector scc{}; - std::function strong_connect; - strong_connect = [&](vertex_id_t vertex) { - if (!seen_vertices.contains(vertex)) { - seen_vertices.insert(vertex); - scc.push_back(vertex); - - for (const auto neighbour : transposed_graph.get_neighbors(vertex)) { - strong_connect(neighbour); - } - } - }; - while (!stack.empty()) { if (!seen_vertices.contains(stack.top())) { scc.clear(); - strong_connect(stack.top()); + make_strongly_connected_component_from_vertex( + stack.top(), transposed_graph, scc, seen_vertices); sccs.push_back(scc); } From 4b53bfc4185744fd5a84c7e197d07b1b51ebc84e Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 20:56:35 +0100 Subject: [PATCH 07/10] Adding vertex to stack after visiting its neighbours in Kosaraju. --- .../algorithm/strongly_connected_components/kosaraju.tpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index a1c29fa5..9ced9469 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -17,12 +17,11 @@ void do_visit_vertex(const vertex_id_t vertex_id, std::stack& stack, std::unordered_set& seen_vertices) { if (!seen_vertices.contains(vertex_id)) { - stack.push(vertex_id); seen_vertices.insert(vertex_id); - for (const auto neighbour : graph.get_neighbors(vertex_id)) { do_visit_vertex(neighbour, graph, stack, seen_vertices); } + stack.push(vertex_id); } } From 54f93433d3c251489f1412be4047b785111a36b8 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 21:00:00 +0100 Subject: [PATCH 08/10] Adding const to result of get_transposed_graph in Kosaraju. --- .../algorithm/strongly_connected_components/kosaraju.tpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index 9ced9469..a8a2591d 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -63,7 +63,7 @@ std::vector> kosarajus_strongly_connected_components( seen_vertices.clear(); - directed_graph transposed_graph = get_transposed_graph(graph); + const auto transposed_graph = get_transposed_graph(graph); std::vector scc{}; while (!stack.empty()) { From aa516c26192e5d52012e7f821e55ac91bfd755b6 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 21:15:20 +0100 Subject: [PATCH 09/10] Updating Kosaraju and Tarjan with new sccs_t type, updating tests and docs with same. --- .../kosarajus.md | 4 ++-- .../strongly-connected-components/tarjan.md | 5 ++--- .../strongly_connected_components/common.h | 11 ++++++++++ .../strongly_connected_components/kosaraju.h | 8 ++++--- .../kosaraju.tpp | 4 ++-- .../strongly_connected_components/tarjan.h | 9 ++++---- .../strongly_connected_components/tarjan.tpp | 4 ++-- .../kosaraju_test.cpp | 3 +-- .../tarjan_test.cpp | 22 +++++++++---------- 9 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 include/graaflib/algorithm/strongly_connected_components/common.h diff --git a/docs/docs/algorithms/strongly-connected-components/kosarajus.md b/docs/docs/algorithms/strongly-connected-components/kosarajus.md index b83796b7..7101b15d 100644 --- a/docs/docs/algorithms/strongly-connected-components/kosarajus.md +++ b/docs/docs/algorithms/strongly-connected-components/kosarajus.md @@ -13,8 +13,8 @@ number of edges in the graph. So it runs in linear time. ```cpp template -std::vector> kosarajus_strongly_connected_components(const directed_graph& graph); +sccs_t kosarajus_strongly_connected_components(const directed_graph& graph); ``` - **graph** The graph for which to compute SCCs. -- **return** A vector of vectors representing SCCs. \ No newline at end of file +- **return** A type consisting of a vector of vectors representing SCCs. \ No newline at end of file diff --git a/docs/docs/algorithms/strongly-connected-components/tarjan.md b/docs/docs/algorithms/strongly-connected-components/tarjan.md index 7b578f72..4b062857 100644 --- a/docs/docs/algorithms/strongly-connected-components/tarjan.md +++ b/docs/docs/algorithms/strongly-connected-components/tarjan.md @@ -13,9 +13,8 @@ number of edges in the graph. So it runs in linear time. ```cpp template -[[nodiscard]] std::vector> -tarjans_strongly_connected_components(const graph& graph); +[[nodiscard]] sccs_t tarjans_strongly_connected_components(const graph& graph); ``` - **graph** The graph for which to compute SCCs. -- **return** A vector of vectors representing SCCs. \ No newline at end of file +- **return** A type consisting of a vector of vectors representing SCCs. \ No newline at end of file diff --git a/include/graaflib/algorithm/strongly_connected_components/common.h b/include/graaflib/algorithm/strongly_connected_components/common.h new file mode 100644 index 00000000..d333ae41 --- /dev/null +++ b/include/graaflib/algorithm/strongly_connected_components/common.h @@ -0,0 +1,11 @@ +#pragma + +#include + +#include + +namespace graaf::algorithm { + +using sccs_t = std::vector>; + +} // namespace graaf::algorithm \ No newline at end of file diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.h b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h index dbed4dc0..5b5e4d25 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.h +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -19,11 +20,12 @@ namespace graaf::algorithm { * @tparam E The edge type of the graph. * @param graph The input directed graph. * - * @return A std::vector of vectors, each of which contains the vertex IDs - * forming a strongly connected component. + * @return An sccs_t, a type representing an std::vector of vectors, + * each of which contains the vertex IDs forming a strongly connected + * component. */ template -std::vector> kosarajus_strongly_connected_components( +sccs_t kosarajus_strongly_connected_components( const directed_graph& graph); } // namespace graaf::algorithm diff --git a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp index a8a2591d..3cde396e 100644 --- a/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp @@ -44,9 +44,9 @@ void make_strongly_connected_component_from_vertex( } // namespace template -std::vector> kosarajus_strongly_connected_components( +sccs_t kosarajus_strongly_connected_components( const directed_graph& graph) { - std::vector> sccs{}; + sccs_t sccs{}; if (graph.get_vertices().size() == 0) { return sccs; diff --git a/include/graaflib/algorithm/strongly_connected_components/tarjan.h b/include/graaflib/algorithm/strongly_connected_components/tarjan.h index 15425276..ff668c37 100644 --- a/include/graaflib/algorithm/strongly_connected_components/tarjan.h +++ b/include/graaflib/algorithm/strongly_connected_components/tarjan.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -17,12 +18,12 @@ namespace graaf::algorithm { * @tparam V Vertex type. * @tparam E Edge type. * @param graph The graph for which to compute SCCs. - * @return std::vector> A vector of vectors - * representing SCCs. + * @return An sccs_t, a type representing an std::vector of vectors, + * each of which contains the vertex IDs forming a strongly connected + * component. */ template -[[nodiscard]] std::vector> -tarjans_strongly_connected_components( +[[nodiscard]] sccs_t tarjans_strongly_connected_components( const graph& graph); } // namespace graaf::algorithm diff --git a/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp b/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp index 597e51a4..1724f7bf 100644 --- a/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp +++ b/include/graaflib/algorithm/strongly_connected_components/tarjan.tpp @@ -10,11 +10,11 @@ namespace graaf::algorithm { template -[[nodiscard]] std::vector> +[[nodiscard]] sccs_t tarjans_strongly_connected_components( const graph& graph) { // Vector to store strongly connected components - std::vector> sccs{}; + sccs_t sccs{}; // Stack to hold vertices during traversal std::stack stack; diff --git a/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp index 01d393aa..a41757b7 100644 --- a/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp +++ b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp @@ -1,11 +1,10 @@ +#include #include #include namespace graaf::algorithm { TEST(KosarajuTest, EqualOutputSCCS) { - using sccs_t = std::vector>; - // GIVEN directed_graph graph{}; const auto vertex_id_1 = graph.add_vertex(1, 1); diff --git a/test/graaflib/algorithm/strongly_connected_components/tarjan_test.cpp b/test/graaflib/algorithm/strongly_connected_components/tarjan_test.cpp index bdd02bab..b3b31501 100644 --- a/test/graaflib/algorithm/strongly_connected_components/tarjan_test.cpp +++ b/test/graaflib/algorithm/strongly_connected_components/tarjan_test.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -23,11 +24,10 @@ struct TarjansStronglyConnectedComponentsTest : public testing::Test { using edge_t = typename T::second_type; }; -bool are_set_vectors_equal(const std::vector>& vec1, - const std::vector>& vec2) { +bool are_set_vectors_equal(const sccs_t& vec1, const sccs_t& vec2) { // Make copies of the input vectors to avoid modifying the originals - std::vector> sorted_vec1 = vec1; - std::vector> sorted_vec2 = vec2; + sccs_t sorted_vec1 = vec1; + sccs_t sorted_vec2 = vec2; // Sort the inner vectors within each vector for (std::vector& inner_vec : sorted_vec1) { @@ -64,7 +64,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, auto sccs = tarjans_strongly_connected_components(graph); // THEN - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector component1 = {0}; expected_sccs.push_back(component1); @@ -101,7 +101,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, TarjansTwoTrianglesTest) { // WHEN auto sccs = tarjans_strongly_connected_components(graph); - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector triangle_1 = {0, 1, 2}; std::vector triangle_2 = {3, 4, 5}; @@ -136,7 +136,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, TarjansLinearChainTest) { auto sccs = tarjans_strongly_connected_components(graph); // THEN - std::vector> expected_sccs; + sccs_t expected_sccs; // Each vertex is its own SCC in a linear chain graph for (const auto& vertex_id : vertex_ids) { @@ -169,7 +169,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, TarjansCycleTest) { auto sccs = tarjans_strongly_connected_components(graph); // THEN - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector component = {0, 1, 2}; expected_sccs.push_back(component); @@ -206,7 +206,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, // THEN // Each vertex itself is considered an SCC. - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector component1 = {0}; expected_sccs.push_back(component1); @@ -246,7 +246,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, auto sccs = tarjans_strongly_connected_components(graph); // THEN - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector component1 = {0}; std::vector component2 = {1}; @@ -288,7 +288,7 @@ TYPED_TEST(TarjansStronglyConnectedComponentsTest, TarjansComplexGraphTest) { auto sccs = tarjans_strongly_connected_components(graph); // THEN - std::vector> expected_sccs; + sccs_t expected_sccs; std::vector component1 = {0, 1, 2}; std::vector component2 = {3, 4, 5}; From aec63865ee1dc81cc42e7d1575a50cfbd8cb3178 Mon Sep 17 00:00:00 2001 From: toadkarter Date: Wed, 11 Oct 2023 21:47:43 +0100 Subject: [PATCH 10/10] Updated tests for Kosaraju. --- .../kosaraju_test.cpp | 119 ++++++++++++++++-- 1 file changed, 109 insertions(+), 10 deletions(-) diff --git a/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp index a41757b7..ada7a863 100644 --- a/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp +++ b/test/graaflib/algorithm/strongly_connected_components/kosaraju_test.cpp @@ -1,9 +1,46 @@ #include #include #include +#include namespace graaf::algorithm { +namespace { + +bool are_set_vectors_equal(const sccs_t& vec1, const sccs_t& vec2) { + // Make copies of the input vectors to avoid modifying the originals + sccs_t sorted_vec1 = vec1; + sccs_t sorted_vec2 = vec2; + + // Sort the inner vectors within each vector + for (std::vector& inner_vec : sorted_vec1) { + std::sort(inner_vec.begin(), inner_vec.end()); + } + for (std::vector& inner_vec : sorted_vec2) { + std::sort(inner_vec.begin(), inner_vec.end()); + } + + // Sort the outer vectors + std::sort(sorted_vec1.begin(), sorted_vec1.end()); + std::sort(sorted_vec2.begin(), sorted_vec2.end()); + + // Compare the sorted vectors for equality + return sorted_vec1 == sorted_vec2; +} + +} // namespace + +TEST(KosarajuTest, EmptySCCS) { + // GIVEN + directed_graph graph{}; + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + + // THEN + ASSERT_TRUE(sccs.empty()); +} + TEST(KosarajuTest, EqualOutputSCCS) { // GIVEN directed_graph graph{}; @@ -32,21 +69,83 @@ TEST(KosarajuTest, EqualOutputSCCS) { sccs_t expected_sccs = {{1, 2, 3}, {4}, {5, 6, 7}, {8}}; // THEN - ASSERT_EQ(sccs.size(), expected_sccs.size()); + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); +} - std::sort(sccs.begin(), sccs.end()); - std::sort(expected_sccs.begin(), expected_sccs.end()); +TEST(KosarajuTest, KiteStructureEqualOutputSCCS) { + // GIVEN + directed_graph graph{}; + const auto vertex_id_1 = graph.add_vertex(1, 1); + const auto vertex_id_2 = graph.add_vertex(2, 2); + const auto vertex_id_3 = graph.add_vertex(3, 3); + const auto vertex_id_4 = graph.add_vertex(4, 4); - for (int i = 0; i < sccs.size(); i++) { - ASSERT_EQ(sccs[i].size(), expected_sccs[i].size()); + graph.add_edge(vertex_id_1, vertex_id_2, 1); + graph.add_edge(vertex_id_2, vertex_id_3, 1); + graph.add_edge(vertex_id_3, vertex_id_1, 1); + graph.add_edge(vertex_id_2, vertex_id_4, 1); - std::sort(sccs[i].begin(), sccs[i].end()); - std::sort(expected_sccs[i].begin(), expected_sccs[i].end()); + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{1, 2, 3}, {4}}; - ASSERT_EQ(sccs[i], expected_sccs[i]); - } + // THEN + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); +} + +TEST(KosarajuTest, TreeScenario) { + // GIVEN + const auto [graph, vertex_ids] = + utils::scenarios::create_tree_scenario>(); + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{0}, {1}, {2}, {3}, {4}}; + + // THEN + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); +} + +TEST(KosarajuTest, SimpleGraphScenario) { + // GIVEN + const auto [graph, vertex_ids] = + utils::scenarios::create_simple_graph_scenario< + directed_graph>(); + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{2, 1, 0}, {3}, {4}}; - ASSERT_EQ(sccs, expected_sccs); + // THEN + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); +} + +TEST(KosarajuTest, FullyConnectedGraphScenario) { + // GIVEN + const auto [graph, vertex_ids] = + utils::scenarios::create_fully_connected_graph_scenario< + directed_graph>(); + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{0}, {1}, {2}, {3}, {4}}; + + // THEN + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); +} + +TEST(KosarajuTest, DisconnectedGraphScenario) { + // GIVEN + const auto [graph, vertex_ids] = + utils::scenarios::create_disconnected_graph_scenario< + directed_graph>(); + + // WHEN + sccs_t sccs = kosarajus_strongly_connected_components(graph); + sccs_t expected_sccs = {{0}, {1}, {2}, {5, 4, 3}}; + + // THEN + ASSERT_TRUE(are_set_vectors_equal(sccs, expected_sccs)); } } // namespace graaf::algorithm \ No newline at end of file