Skip to content

Commit

Permalink
introduce tarjan's algorithm (#103)
Browse files Browse the repository at this point in the history
* implement tarjan's algorithm to find sccs for directed graphs

* implement tarjan's algorithm to find cut vertices/bridges/vdcc/edcc
  for undirected graphs

* formatting
  • Loading branch information
suncanghuai committed Jul 2, 2023
1 parent 98432ff commit 4b2cb93
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 4 deletions.
182 changes: 181 additions & 1 deletion include/Graph/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,21 @@ class Graph {
*/
virtual const DijkstraResult dijkstra(const Node<T> &source,
const Node<T> &target) const;
/**
* @brief This function runs the tarjan algorithm and returns different types
* of results depending on the input parameter typeMask.
*
* @param typeMask each bit of typeMask within valid range represents a kind
* of results should be returned.
*
* Note: No Thread Safe
*
* @return The types of return include strongly connected components
* (only for directed graphs) and cut vertices、 bridges、edge
* biconnected components and vertice biconnected components
* (only for undirected graphs).
*/
virtual const TarjanResult<T> tarjan(const unsigned int typeMask) const;
/**
* @brief Function runs the bellman-ford algorithm for some source node and
* target node in the graph and returns the shortest distance of target
Expand Down Expand Up @@ -2540,7 +2555,7 @@ bool Graph<T>::isUndirectedGraph() const {
}

template <typename T>
void Graph<T>::reverseDirectedGraph(){
void Graph<T>::reverseDirectedGraph() {
if (!isDirectedGraph()) {
throw std::runtime_error(ERR_UNDIR_GRAPH);
}
Expand Down Expand Up @@ -2636,6 +2651,171 @@ bool Graph<T>::isStronglyConnectedGraph() const {
}
}

template <typename T>
const TarjanResult<T> Graph<T>::tarjan(const unsigned int typeMask) const {
TarjanResult<T> result;
result.success = false;
bool isDirected = this->isDirectedGraph();
if (isDirected) {
// check whether targetMask is a subset of the mask for directed graph
unsigned int directedMask = TARJAN_FIND_SCC;
if ((typeMask | directedMask) != directedMask) {
result.errorMessage = ERR_DIR_GRAPH;
return result;
}
} else {
// check whether targetMask is a subset of the mask for undirected graph
unsigned int undirectedMask = (TARJAN_FIND_CUTV | TARJAN_FIND_BRIDGE |
TARJAN_FIND_VBCC | TARJAN_FIND_EBCC);
if ((typeMask | undirectedMask) != undirectedMask) {
result.errorMessage = ERR_UNDIR_GRAPH;
return result;
}
}

const auto &adjMatrix = getAdjMatrix();
const auto &nodeSet = getNodeSet();
std::unordered_map<size_t, int>
discoveryTime; // the timestamp when a node is visited
std::unordered_map<size_t, int>
lowestDisc; // the lowest discory time of all
// reachable nodes from current node
int timestamp = 0;
size_t rootId = 0;
std::stack<Node<T>> sccNodeStack;
std::stack<Node<T>> ebccNodeStack;
std::stack<Node<T>> vbccNodeStack;
std::unordered_set<size_t> inStack;
std::function<void(const shared<const Node<T>>, const shared<const Edge<T>>)>
dfs_helper = [this, typeMask, isDirected, &dfs_helper, &adjMatrix,
&discoveryTime, &lowestDisc, &timestamp, &rootId,
&sccNodeStack, &ebccNodeStack, &vbccNodeStack, &inStack,
&result](const shared<const Node<T>> curNode,
const shared<const Edge<T>> prevEdge) {
// record the visited time of current node
discoveryTime[curNode->getId()] = timestamp;
lowestDisc[curNode->getId()] = timestamp;
timestamp++;
if (typeMask & TARJAN_FIND_SCC) {
sccNodeStack.emplace(*curNode);
inStack.emplace(curNode->getId());
}
if (typeMask & TARJAN_FIND_EBCC) {
ebccNodeStack.emplace(*curNode);
}
if (typeMask & TARJAN_FIND_VBCC) {
vbccNodeStack.emplace(*curNode);
}
// travel the neighbors
int numSon = 0;
bool nodeIsAdded =
false; // whether a node has been marked as a cut vertice
if (adjMatrix->find(curNode) != adjMatrix->end()) {
for (const auto &[neighborNode, edge] : adjMatrix->at(curNode)) {
if (!discoveryTime.count(neighborNode->getId())) {
dfs_helper(neighborNode, edge);
lowestDisc[curNode->getId()] =
std::min(lowestDisc[curNode->getId()],
lowestDisc[neighborNode->getId()]);

if (typeMask & TARJAN_FIND_BRIDGE) {
// lowestDisc of neighbor node is larger than that of current
// node means we can travel back to a visited node only through
// this edge
if (discoveryTime[curNode->getId()] <
lowestDisc[neighborNode->getId()]) {
result.bridges.emplace_back(*edge);
}
}

if ((typeMask & TARJAN_FIND_CUTV) && (nodeIsAdded == false)) {
if (curNode->getId() == rootId) {
numSon++;
// a root node is a cut vertices only when it connects at
// least two connected components
if (numSon == 2) {
nodeIsAdded = true;
result.cutVertices.emplace_back(*curNode);
}
} else {
if (discoveryTime[curNode->getId()] <=
lowestDisc[neighborNode->getId()]) {
nodeIsAdded = true;
result.cutVertices.emplace_back(*curNode);
}
}
}

if (typeMask & TARJAN_FIND_VBCC) {
if (discoveryTime[curNode->getId()] <=
lowestDisc[neighborNode->getId()]) {
// if current node is a cut vertice or the root node, the vbcc
// a vertice-biconnect-component which contains the neighbor
// node
std::vector<Node<T>> vbcc;
while (true) {
// pop a top node out of stack until
// the neighbor node has been poped out
Node<T> nodeAtTop = sccNodeStack.top();
vbccNodeStack.pop();
vbcc.emplace_back(nodeAtTop);
if (nodeAtTop == *neighborNode) {
break;
}
}
vbcc.emplace_back(*curNode);
result.verticeBiconnectedComps.emplace_back(std::move(vbcc));
}
}
} else if ((edge != prevEdge) &&
((isDirected == false) ||
(inStack.count(neighborNode->getId())))) {
// it's not allowed to go through the previous edge back
// for a directed graph, it's also not allowed to visit
// a node that is not in stack
lowestDisc[curNode->getId()] =
std::min(lowestDisc[curNode->getId()],
lowestDisc[neighborNode->getId()]);
}
}
}
// find sccs for a undirected graph is very similar with
// find ebccs for a directed graph
if ((typeMask & TARJAN_FIND_SCC) || (typeMask & TARJAN_FIND_EBCC)) {
std::stack<Node<T>> &nodeStack =
(typeMask & TARJAN_FIND_SCC) ? sccNodeStack : ebccNodeStack;
if (discoveryTime[curNode->getId()] == lowestDisc[curNode->getId()]) {
std::vector<Node<T>> connectedComp;
while (true) {
// pop a top node out of stack until
// the current node has been poped out
Node<T> nodeAtTop = nodeStack.top();
nodeStack.pop();
connectedComp.emplace_back(nodeAtTop);
if (nodeAtTop == *curNode) {
break;
}
}
// store this component in result
(typeMask & TARJAN_FIND_SCC)
? result.stronglyConnectedComps.emplace_back(
std::move(connectedComp))
: result.edgeBiconnectedComps.emplace_back(
std::move(connectedComp));
}
}
};

for (const auto &node : nodeSet) {
if (!discoveryTime.count(node->getId())) {
rootId = node->getId();
dfs_helper(node, nullptr);
}
}

return result;
}

template <typename T>
TopoSortResult<T> Graph<T>::topologicalSort() const {
TopoSortResult<T> result;
Expand Down
47 changes: 44 additions & 3 deletions include/Utility/Typedef.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "ConstValue.hpp"
#include "PointerHash.hpp"
Expand All @@ -35,10 +37,10 @@ namespace CXXGraph {
template <typename T>
using unique = std::unique_ptr<T>;
template <typename T>
using shared= std::shared_ptr<T>;
using shared = std::shared_ptr<T>;

using std::make_unique;
using std::make_shared;
using std::make_unique;

template <typename T>
class Node;
Expand All @@ -62,6 +64,14 @@ enum E_InputOutputFormat {

typedef E_InputOutputFormat InputOutputFormat;

/// specify the type of results returnde by tarjan's algorithm
enum TarjanAlgorithmTypes {
TARJAN_FIND_SCC = (1 << 0),
TARJAN_FIND_CUTV = (1 << 1),
TARJAN_FIND_BRIDGE = (1 << 2),
TARJAN_FIND_VBCC = (1 << 3),
TARJAN_FIND_EBCC = (1 << 4)
};
/////////////////////////////////////////////////////
// Structures ///////////////////////////////////////

Expand Down Expand Up @@ -190,6 +200,35 @@ struct SCCResult_struct {
template <typename T>
using SCCResult = SCCResult_struct<T>;

/// Struct that contains the information about TopologicalSort's Algorithm
/// results
template <typename T>
struct TarjanResult_struct {
bool success =
false; // TRUE if the function does not return error, FALSE otherwise
std::string errorMessage = ""; // message of error
Components<T>
stronglyConnectedComps; // vectors that store nodes belong to same SCC
// (valid only if a graph is directed and flag
// TARJAN_FIND_SCC is set)
Components<T>
verticeBiconnectedComps; // vectors that store nodes belong to same v-bcc
// (valid only if a graph is undirected and flag
// TARJAN_FIND_VBCC is set)
Components<T>
edgeBiconnectedComps; // vectors that store nodes belong to same e-bcc
// (valid only if a graph is undirected and flag
// TARJAN_FIND_EBCC is set)
std::vector<Node<T>> cutVertices; // a vector that stores cut vertices
// (valid only is a graph is undirected and
// flag TARJAN_FIND_CUTV is set)
std::vector<Edge<T>> bridges; // a vector that stores bridges
// (valid only is a graph is undirected and
// flag TRAJAN_FIND_BRIDGES is set)
};
template <typename T>
using TarjanResult = TarjanResult_struct<T>;

/// Struct that contains the information about Best First Search Algorithm
/// results
template <typename T>
Expand All @@ -209,7 +248,9 @@ using BestFirstSearchResult = BestFirstSearchResult_struct<T>;

template <typename T>
using AdjacencyMatrix = std::unordered_map<
shared<const Node<T>>, std::vector<std::pair<shared<const Node<T>>, shared<const Edge<T>>>>, nodeHash<T>>;
shared<const Node<T>>,
std::vector<std::pair<shared<const Node<T>>, shared<const Edge<T>>>>,
nodeHash<T>>;

template <typename T>
using PartitionMap =
Expand Down

0 comments on commit 4b2cb93

Please sign in to comment.