Skip to content

Commit

Permalink
introduce tarjan's algorithm (ZigRazor#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 facb4eb
Show file tree
Hide file tree
Showing 57 changed files with 225 additions and 4 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/DFS_BM.cpp.40AAB3F7C1259F0B.idx
Binary file not shown.
Binary file added .cache/clangd/index/Dial_BM.cpp.F3D5FBFDB3E5F774.idx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/EBV.hpp.9F21401B505F63A9.idx
Binary file not shown.
Binary file added .cache/clangd/index/Edge.hpp.36CB9FCC5873EF5D.idx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/HDRF.hpp.17F661AEF332D941.idx
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/Node.hpp.1B18E7AA69513C9A.idx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/Prim_BM.cpp.6A59BB33690C7587.idx
Binary file not shown.
Binary file added .cache/clangd/index/Reader.hpp.65EB828154860F82.idx
Binary file not shown.
Binary file added .cache/clangd/index/Record.hpp.875077E2C2839158.idx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .cache/clangd/index/Writer.hpp.A8B46E75D2EE1A12.idx
Binary file not shown.
Binary file added .cache/clangd/index/main.cpp.32179A7A9938FDB5.idx
Binary file not shown.
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 facb4eb

Please sign in to comment.