Finish Tarjan algorithm and add tests

This commit is contained in:
stefiosif
2022-05-07 17:05:43 +03:00
parent 122d11b189
commit 8a52c80bc8
5 changed files with 145 additions and 117 deletions

View File

@@ -4,71 +4,80 @@
#include "graph/graph.h" #include "graph/graph.h"
using namespace graph; using namespace graph;
#include <stack> #include <algorithm>
#include <iostream> #include <iostream>
#include <ranges> #include <ranges>
#include <iterator> #include <stack>
#include <algorithm>
namespace algorithm { namespace algo {
template<typename T> template<typename T>
class Tarjan { class Tarjan {
public: public:
Tarjan(Graph<T> G) : G(G) {} Tarjan(Graph<T> G) : G(G) {}
void findSCC(); //
std::vector<std::vector<T>> run();
void strongConnect(Vertex<T>& v); //
void strongConnect(const T& v);
std::uint16_t index = 0;
std::stack<Vertex<T>> S;
private: private:
// Necessary info about vertices when running Tarjan's algorithm
struct Payload {
std::int16_t index = -1;
std::int16_t lowlink = -1;
bool onStack = false;
};
Graph<T> G; Graph<T> G;
std::stack<T> S;
std::int16_t index = 0;
std::map<T, Payload> vp;
std::vector<std::vector<T>> SCCs;
}; };
template<typename T> template<typename T>
void Tarjan<T>::strongConnect(Vertex<T>& v) { inline void Tarjan<T>::strongConnect(const T& v) {
v.index, v.lowlink = index++; vp[v].index = vp[v].lowlink = index++;
v.onStack = true; vp[v].onStack = true;
S.push(v); S.push(v);
for (auto& w : v.edges) { for (const auto& w : G.adjMatrix[v]) {
if (w.index == -1) { if (vp[w].index == -1) {
// successor w has not yet been visited, recurse on it
strongConnect(w); strongConnect(w);
v.lowlink = std::min(v.lowlink, w.lowlink); vp[v].lowlink = std::min(vp[v].lowlink, vp[w].lowlink);
} else if (w.onStack) { } else if (vp[w].onStack) {
// successor w is in stack S and hence in the current SCC vp[v].lowlink = std::min(vp[v].lowlink, vp[w].index);
// if w is not on stack, then (v, w) is an edge pointing to an SCC
// already found and must be ignored
v.lowlink = std::min(v.lowlink, w.index);
} }
} }
// if v is a root node, pop the stack and generate an SCC // If v is a root node, pop the stack and generate an SCC
if (v.lowlink = v.index) { if (vp[v].lowlink == vp[v].index) {
// start a new SCC std::vector<T> SCC;
Vertex<T> w; bool finished = false;
do { do {
w = S.top(); const auto& w = S.top();
S.pop(); S.pop();
// add w to current SCC vp[w].onStack = false;
SCC.push_back(w);
} while (w != v); finished = vp[w].index == vp[v].index;
} while (!finished);
SCCs.push_back(SCC);
} }
} }
template<typename T> template<typename T>
void Tarjan<T>::findSCC() { inline std::vector<std::vector<T>> Tarjan<T>::run() {
for (auto& vertex : G.vertices) { for (const auto& v : G.vertices) {
if (vertex.index == -1) { if (vp[v].index == -1) {
strongConnect(vertex); strongConnect(v);
}
} }
} }
} // namespace algorithm return SCCs;
}
} // namespace algo
#endif #endif

View File

@@ -1,12 +1,11 @@
#ifndef GRAPH_H_ #ifndef GRAPH_H_
#define GRAPH_H_ #define GRAPH_H_
#include "vertex.h"
#include <map> #include <map>
#include <set> #include <set>
#include <vector>
#include <utility> #include <utility>
#include <vector>
#include <iostream>
namespace graph { namespace graph {
@@ -16,50 +15,44 @@ public:
Graph() = default; Graph() = default;
// Add vertex v // Add vertex v
void insert(Vertex<T>& v); void insert(const T& v);
// Add edge between v and u // Add edge between v and u
void insert(Vertex<T> v, Vertex<T> u); void insert(const T& v, const T& u);
// Delete vertex v // Delete vertex v
void erase(Vertex<T>& v); void erase(T& v);
// Delete edge between v and u // Delete edge between v and u
void erase(Vertex<T>& v, Vertex<T>& u); void erase(T& v, T& u);
// Return true if v and u are connected
bool connected(const Vertex<T>& v, const Vertex<T>& u) const;
// Adjacency matrix representation // Adjacency matrix representation
std::vector<Vertex<T>> vertices; std::set<T> vertices;
std::map<T, std::vector<T>> adjMatrix;
}; };
template<typename T> template<typename T>
inline void Graph<T>::insert(Vertex<T>& v) { inline void Graph<T>::insert(const T& v) {
vertices.push_back(v); vertices.insert(v);
} }
template<typename T> template<typename T>
inline void Graph<T>::insert(Vertex<T> v, Vertex<T> u) { inline void Graph<T>::insert(const T& v, const T& u) {
v.edges.push_back(u); vertices.insert(v);
vertices.push_back(v); vertices.insert(u);
adjMatrix[v].push_back(u);
} }
template<typename T> template<typename T>
inline void Graph<T>::erase(Vertex<T>& v) { inline void Graph<T>::erase(T& v) {
// //
} }
template<typename T> template<typename T>
inline void Graph<T>::erase(Vertex<T>& v, Vertex<T>& u) { inline void Graph<T>::erase(T& v, T& u) {
// //
} }
template<typename T>
inline bool Graph<T>::connected(const Vertex<T>& v, const Vertex<T>& u) const {
return false;
}
} // namespace graph } // namespace graph
#endif #endif

View File

@@ -1,36 +0,0 @@
#ifndef VERTEX_H_
#define VERTEX_H_
#include <cstdint>
#include <compare>
#include <ostream>
#include <vector>
namespace graph {
template<typename T>
class Vertex {
public:
Vertex() = default;
Vertex(T v) : v(v) {}
T v;
std::uint16_t index = -1;
std::uint16_t lowlink = -1;
bool onStack = false;
std::vector<Vertex<T>> edges;
auto operator<=>(const Vertex&) const = default;
template<typename T>
friend inline std::ostream& operator<<(std::ostream& os, const Vertex<T>& v);
};
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Vertex<T>& vout) {
return os << vout.v;
}
} // namespace graph
#endif

73
test/algorithm_test.cc Normal file
View File

@@ -0,0 +1,73 @@
#include <doctest/doctest.h>
#include "graph/graph.h"
using namespace graph;
#include "algorithm/tarjan.h"
TEST_SUITE("Algorithms") {
TEST_CASE("Tarjan T_1") {
Graph<std::uint16_t> G;
G.insert(1, 2);
G.insert(2, 4);
G.insert(2, 3);
G.insert(3, 5);
G.insert(4, 1);
G.insert(5, 7);
G.insert(5, 9);
G.insert(6, 8);
G.insert(7, 3);
G.insert(8, 9);
G.insert(9, 6);
REQUIRE_EQ(G.vertices.size(), 9);
algo::Tarjan<std::uint16_t> tarjan(G);
auto result = tarjan.run();
std::sort(result.begin(), result.end());
std::for_each(result.begin(), result.end(), [&](std::vector<std::uint16_t>& row) {
std::sort(row.begin(), row.end());
});
std::vector<std::vector<std::uint16_t>> expected{
{1, 2, 4},
{3, 5, 7},
{6, 8, 9}
};
CHECK_EQ(expected, result);
}
TEST_CASE("Tarjan T_2") {
Graph<std::uint16_t> G;
G.insert(1, 4);
G.insert(1, 2);
G.insert(2, 5);
G.insert(3, 1);
G.insert(4, 3);
G.insert(4, 6);
G.insert(5, 7);
G.insert(6, 3);
G.insert(7, 2);
REQUIRE_EQ(G.vertices.size(), 7);
algo::Tarjan<std::uint16_t> tarjan(G);
auto result = tarjan.run();
std::sort(result.begin(), result.end());
std::for_each(result.begin(), result.end(), [&](std::vector<std::uint16_t>& row) {
std::sort(row.begin(), row.end());
});
std::vector<std::vector<std::uint16_t>> expected{
{1, 3, 4, 6},
{2, 5, 7}
};
CHECK_EQ(expected, result);
}
}

View File

@@ -2,33 +2,22 @@
#include "graph/graph.h" #include "graph/graph.h"
using namespace graph; using namespace graph;
#include "algorithm/tarjan.h"
#include <cstdint> TEST_SUITE("Graph test.") {
#include <iostream>
TEST_SUITE("Testing Graph.") { TEST_CASE("Insertion.") {
TEST_CASE("Insert vertices/edges.") {
Graph<std::uint16_t> G; Graph<std::uint16_t> G;
//G.insert(1);
//G.insert(1, 2);
//G.insert(1, 4);
//G.insert(1, 6);
//G.insert(2, 4);
//G.insert(2, 5);
//G.insert(3, 4);
//G.insert(3, 6);
//G.insert(4, 6);
//G.insert(5, 6);
G.insert(1, 2);
G.insert(1, 4);
G.insert(1, 6);
G.insert(2, 4);
G.insert(2, 5);
G.insert(3, 4);
G.insert(3, 6);
G.insert(4, 6);
G.insert(5, 6);
Vertex<std::uint16_t> v1(1);
Vertex<std::uint16_t> v2(3);
Vertex<std::uint16_t> v3(5);
algorithm::Tarjan<std::uint16_t> tarjan(G);
tarjan.findSCC();
// CHECK_EQ(G.connected(1, 5), true);
} }
} }