Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce tarjan's algorithm (#103) #322

Merged
merged 3 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 186 additions & 1 deletion include/Graph/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#ifndef __CXXGRAPH_GRAPH_H__
#define __CXXGRAPH_GRAPH_H__

#include <cstdio>
#pragma once

#include <limits.h>
Expand Down Expand Up @@ -358,6 +359,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 +2556,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 +2652,175 @@ 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 discovery 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 = vbccNodeStack.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();
if (typeMask & TARJAN_FIND_SCC) {
inStack.erase(nodeAtTop.getId());
}
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);
}
}

result.success = true;
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
Loading