Skip to content

Commit

Permalink
feat: Tarjan's SCC algorithm (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndcroos authored Aug 19, 2023
1 parent 093151f commit 9c90948
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Algorithms implemented in the Graaf library include the following. For more info
- Bellman-Ford
3. [**Cycle Detection Algorithms**](https://bobluppes.github.io/graaf/docs/category/cycle-detection-algorithms):
- DFS-Based Cycle Detection
4. [**Strongly Connected Components Algorithms**](https://bobluppes.github.io/graaf/docs/category/strongly-connected-components):
- Tarjan's Strongly Connected Components

# Contributing
The Graaf library welcomes contributions 🎊
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Strongly Connected Component Algorithms",
"position": 4,
"link": {
"type": "generated-index"
}
}

22 changes: 22 additions & 0 deletions docs/docs/algorithms/strongly-connected-components/tarjan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
sidebar_position: 1
---

# Tarjan's Strongly Connected Components

Tarjan'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.

Tarjan'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/Tarjan%27s_strongly_connected_components_algorithm)

## Syntax

```cpp
template <typename V, typename E>
[[nodiscard]] std::vector<std::vector<vertex_id_t>>
tarjans_strongly_connected_components(const graph<V, E, graph_type::DIRECTED>& graph);
```
- **graph** The graph for which to compute SCCs.
- **return** A vector of vectors representing SCCs.
30 changes: 30 additions & 0 deletions include/graaflib/algorithm/strongly_connected_components.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <graaflib/graph.h>
#include <graaflib/types.h>

namespace graaf::algorithm {

/**
* Computes the Strongly Connected Components (SCCs) of a graph using Tarjan's
* algorithm.
*
* This function takes a graph and returns a vector of vectors representing the
* SCCs. Each inner vector contains the vertices of a strongly connected
* component, and the outer vector contains all the strongly connected
* components in the graph.
*
* @tparam V Vertex type.
* @tparam E Edge type.
* @param graph The graph for which to compute SCCs.
* @return std::vector<std::vector<vertex_id_t>> A vector of vectors
* representing SCCs.
*/
template <typename V, typename E>
[[nodiscard]] std::vector<std::vector<vertex_id_t>>
tarjans_strongly_connected_components(
const graph<V, E, graph_type::DIRECTED>& graph);

} // namespace graaf::algorithm

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

#include <graaflib/algorithm/strongly_connected_components.h>

#include <functional> // For std::function
#include <stack>
#include <unordered_map>
#include <vector>

namespace graaf::algorithm {

template <typename V, typename E>
[[nodiscard]] std::vector<std::vector<vertex_id_t>>
tarjans_strongly_connected_components(
const graph<V, E, graph_type::DIRECTED>& graph) {
// Vector to store strongly connected components
std::vector<std::vector<vertex_id_t>> sccs;

// Stack to hold vertices during traversal
std::stack<vertex_id_t> stack;

// Maps to keep track of indices, low-link values, and stack membership
std::unordered_map<vertex_id_t, size_t> indices;
std::unordered_map<vertex_id_t, size_t> low_links;
std::unordered_map<vertex_id_t, bool> on_stack;

// Counter for indexing vertices
size_t index_counter = 0;

// Lambda function for the strong connect traversal
std::function<void(vertex_id_t)> strong_connect;

strong_connect = [&](vertex_id_t vertex) {
// Set indices and low-link values for the current vertex
indices[vertex] = index_counter;
low_links[vertex] = index_counter;
index_counter++;

// Push the vertex onto the stack and mark it as on-stack
stack.push(vertex);
on_stack[vertex] = true;

// Traverse neighbors
for (const auto neighbor : graph.get_neighbors(vertex)) {
if (!indices.contains(neighbor)) {
// Neighbor has not yet been visited; recurse on it
strong_connect(neighbor);
low_links[vertex] = std::min(low_links[vertex], low_links[neighbor]);
} else if (on_stack[neighbor]) {
// Neighbor is in stack and hence in the current SCC
low_links[vertex] = std::min(low_links[vertex], indices[neighbor]);
}
}

// If low-link and index match, a strongly connected component is found
if (low_links[vertex] == indices[vertex]) {
std::vector<vertex_id_t> scc;
vertex_id_t top;
// Pop vertices from the stack to form the SCC
do {
top = stack.top();
stack.pop();
on_stack[top] = false;
scc.push_back(top); // Add to current strongly connected component
} while (top != vertex);

// Add the SCC to the list of SCCs
sccs.push_back(scc);
}
};

// Traverse all vertices to find SCCs
for (const auto& [vertex_id, vertex] : graph.get_vertices()) {
if (!indices.contains(vertex_id)) {
strong_connect(vertex_id);
}
}

return sccs;
}

} // namespace graaf::algorithm
Loading

0 comments on commit 9c90948

Please sign in to comment.