diff --git a/.clang-format b/.clang-format index b10137e..3f417c3 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ --- BasedOnStyle: Google -ColumnLimit: 79 +ColumnLimit: 80 IndentWidth: 4 --- Language: Cpp diff --git a/graph/bfs.h b/graph/bfs.h index 52dc124..6d80172 100644 --- a/graph/bfs.h +++ b/graph/bfs.h @@ -9,7 +9,7 @@ #include "comm.h" /** - * BFS over directed graphs. + * BFS over directed graphs, i.e., only search from out-neighbors of each node. */ template class DirBFS { diff --git a/graph/cncom.h b/graph/cncom.h new file mode 100644 index 0000000..7efc18f --- /dev/null +++ b/graph/cncom.h @@ -0,0 +1,136 @@ +/** + * Copyright (C) by J.Z. (03/31/2018 16:50) + * Distributed under terms of the MIT license. + */ + +#ifndef __CNCOM_H__ +#define __CNCOM_H__ + +#include "comm.h" + +namespace graph { + +/** + * Strongly connected componetn DFS visitor. Using Tarjan's SCC Algorithm. Refer + * to Knuth's book "The Stanford Graph Base", pp.512-519. + */ +template +class SCCVisitor { +public: + class Element { + public: + int id, parent, rank, min, edge, deg; + + public: + Element(int v = -1, int r = -1, int d = 0) + : id(v), parent(-1), rank(r), min(v), edge(0), deg(d) {} + }; /* Node */ + +private: + const Graph& graph_; + int rank_; + std::unordered_map record_; + std::stack stack_; + +public: + std::vector> nd_cc_vec_; // node-component pairs + +public: + SCCVisitor(const Graph& graph) : graph_(graph), rank_(0) { + nd_cc_vec_.reserve(graph_.getNodes()); + } + + void performDFS() { + for (auto it = graph_.beginNI(); it != graph_.endNI(); it++) { + int v = it->first; + if (record_.find(v) == record_.end()) { + makeActive(v); + while (v != -1) v = explore(v); + } + } + // topology sort + std::reverse(nd_cc_vec_.begin(), nd_cc_vec_.end()); + } + + /** + * Make vertex v active. + */ + void makeActive(const int v, const int parent = -1) { + record_[v] = Element(v, ++rank_, graph_[v].getOutDeg()); + if (parent != -1) record_[v].parent = parent; + stack_.push(v); + } + + /** + * Explore one step from current vertex v, possible move to another current + * vertex and calling it v. Return the next vertex to be explored. + */ + int explore(const int v) { + Element& ve = record_[v]; + if (ve.edge != ve.deg) { // v has untagged edge + const int u = graph_[v].getOutNbr(ve.edge++); + if (record_.find(u) != record_.end()) { // seen u before + int u_rank = record_[u].rank, v_min_rank = record_[ve.min].rank; + if (u_rank < v_min_rank) ve.min = u; + return v; + } else { // u is presently unseen + makeActive(u, v); + // u become the current vertex, i.e., the node to be explored + return u; + } + } else { // all edges from v are tagged, so v matures + int u = ve.parent; + if (ve.min == v) { // subtree with root v forms a SCC + finish(v); + } else { // the edge from u to v has just matured, make v.min + // visible from u + int v_min_rank = record_[ve.min].rank, &u_min = record_[u].min, + u_min_rank = record_[u_min].rank; + if (v_min_rank < u_min_rank) u_min = ve.min; + } + return u; + } + } + + /** + * Remove v and all its successors on the active stack from the tree, and + * mark them as a SCC. + */ + void finish(const int v) { + while (true) { + int t = stack_.top(); + stack_.pop(); + nd_cc_vec_.emplace_back(t, v); + record_[t].rank = INT_MAX; + record_[t].parent = v; + if (t == v) break; + } + } + + /** + * Print out one representative edge between two SCCs. + */ + void getConnections(std::vector>& cc_edge_vec) { + int cur_cc = -1; + std::unordered_set discovered_ccs; + for (auto&& it = nd_cc_vec_.begin(); it != nd_cc_vec_.end(); it++) { + int v = it->first; + int cc_from = it->second; + if (cc_from != cur_cc) { + cur_cc = cc_from; + discovered_ccs.clear(); + } + discovered_ccs.insert(cc_from); + for (int d = 0; d < record_[v].deg; d++) { + int cc_to = record_[graph_[v].getOutNbr(d)].parent; + if (discovered_ccs.find(cc_to) == discovered_ccs.end()) { + discovered_ccs.insert(cc_to); + cc_edge_vec.emplace_back(cc_from, cc_to); + } + } + } + } +}; + +} /* namespace graph */ +#endif /* __CNCOM_H__ */ diff --git a/graph/comm.h b/graph/comm.h index e37a31c..9217493 100644 --- a/graph/comm.h +++ b/graph/comm.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/graph/graph.h b/graph/graph.h index 440b96c..62e9f0e 100644 --- a/graph/graph.h +++ b/graph/graph.h @@ -13,4 +13,6 @@ #include "triad.h" #include "network.h" +#include "cncom.h" + #endif /* __GRAPH_H__ */ diff --git a/test/test_graph.cpp b/test/test_graph.cpp index 7e87164..0f5a61b 100644 --- a/test/test_graph.cpp +++ b/test/test_graph.cpp @@ -5,9 +5,55 @@ using namespace graph; -int main(int argc, char* argv[]) { - osutils::Timer tm; +/** + * test SCC on directed graph + */ +void test_scc() { + DGraph g; + g.addEdge(0, 1); + g.addEdge(0, 5); + g.addEdge(2, 0); + g.addEdge(2, 3); + g.addEdge(3, 2); + g.addEdge(3, 5); + g.addEdge(4, 2); + g.addEdge(4, 3); + g.addEdge(5, 4); + g.addEdge(6, 0); + g.addEdge(6, 4); + g.addEdge(6, 9); + g.addEdge(7, 6); + g.addEdge(7, 8); + g.addEdge(8, 7); + g.addEdge(8, 9); + g.addEdge(9, 10); + g.addEdge(9, 11); + g.addEdge(10, 12); + g.addEdge(11, 4); + g.addEdge(11, 12); + g.addEdge(12, 9); + SCCVisitor visitor(g); + visitor.performDFS(); + auto& nd_cc_vec = visitor.nd_cc_vec_; + int cc = -1; + for (auto it = nd_cc_vec.begin(); it!=nd_cc_vec.end(); it++) { + int v = it->first, c = it->second; + if (c != cc) { + cc = c; + printf("\nSCC[%d]: ", c); + } + printf(" %d", v); + } + printf("\n"); + + std::vector> cc_edge_vec; + visitor.getConnections(cc_edge_vec); + for (auto it = cc_edge_vec.begin(); it!=cc_edge_vec.end(); it++) { + printf("%d --> %d\n", it->first, it->second); + } +} +void test_net() { DatNet>> net; net.addEdge(1, 2, 1); net.addEdge(2, 3, 1); @@ -15,12 +61,19 @@ int main(int argc, char* argv[]) { net.addEdge(4, 5, 1); printf("%d, %d\n", net.getNodes(), net.getEdges()); - for (auto&& ei=net.beginEI(); ei!=net.endEI(); ei++) { + for (auto&& ei = net.beginEI(); ei != net.endEI(); ei++) { printf("%d, %d, %d\n", ei.getSrcID(), ei.getDstID(), ei.getDat()); } - for (auto&& ni=net.beginNI(); ni!=net.endNI(); ni++) { + for (auto&& ni = net.beginNI(); ni != net.endNI(); ni++) { printf("%d, %d\n", ni->first, ni->second.getOutDeg()); } +} + +int main(int argc, char* argv[]) { + // osutils::Timer tm; + + test_scc(); + return 0; }