# | Likes | Tech tags | Title | Creator | Created date |
---|---|---|---|---|---|
1 | 0 |
2022-11-21 11:38
|
A slightly modified version of the Tarjan's bridge-finding algorithm that finds all bridges in an undirected graph in \(O(n+m)\) time (\(n\) is the number of nodes, \(m\) is the number of edges). It constructs the rooted forests and finds the bridges in the same depth-first search call, and it uses node depths instead of pre-order labels to find the bridges.
Caveat: The algorithm is recursive, which means the function calls get stored in the call stack. The maximum call-stack size is usually 1MB on Windows and 10MB on Linux, which can set a severe restriction on the maximum depth the algorithm can reach before the program crashes. To get around it, either increase the size of the call stack for the program, or use an iterative algorithm instead.
#include<vector>
#include<deque>
#include<algorithm>
class BridgesGraph {
struct Edge { // Bidirectional edge
int u;
int v;
int get_from(int from_node) {
if(from_node == u) {
return v;
}
return u;
}
};
struct Node {
int depth_ = 0; // Depth of the node in the spanning forest
int upper_depth_reached_ = 0; // The maximum depth that can be reached from this node's spanning forest subtree
bool explored_ = false;
int index;
std::vector<Edge*> edges;
};
std::deque<Edge> edges_; // We use deque to prevent pointer invalidation when exceeding capacity
std::vector<Node> nodes_;
int _bridges_count_;
void _find_bridges_dfs(Node* node, Edge* last_edge_used = NULL) {
node->explored_ = true;
node->upper_depth_reached_ = node->depth_;
for(Edge* edge : node->edges) if(edge != last_edge_used) { // Go through all edges expect the one from parent
Node* next = &nodes_[edge->get_from(node->index)];
if(!next->explored_) { // Next node not explored, so this edge belongs to the spanning forest
next->depth_ = node->depth_+1;
_find_bridges_dfs(next, edge);
node->upper_depth_reached_ = std::min(node->upper_depth_reached_, next->upper_depth_reached_);
if(next->upper_depth_reached_ == next->depth_) {
// Subtree has no edges going up, so this edge is a bridge
_bridges_count_++;
}
} else { // Node is explored, so this edge links to subtree or to nodes up the spanning tree
node->upper_depth_reached_ = std::min(node->upper_depth_reached_, next->depth_);
}
}
}
public:
BridgesGraph(int num_nodes) {
nodes_.resize(num_nodes);
for(int i=0; i<num_nodes; i++) {
nodes_[i].index = i;
}
}
void add_edge(int u, int v) {
edges_.push_back({u, v});
Edge* edge = &edges_.back();
nodes_[u].edges.push_back(edge);
nodes_[v].edges.push_back(edge);
}
int count_bridges() {
for(Node& node : nodes_) {
node.depth_ = 0;
node.explored_ = false;
}
_bridges_count_ = 0;
for(Node& node : nodes_) if(!node.explored_) {
_find_bridges_dfs(&node);
}
return _bridges_count_;
}
};
int main() {
BridgesGraph graph(6);
graph.add_edge(0, 1);
graph.add_edge(0, 2);
graph.add_edge(1, 2);
graph.add_edge(2, 3);
graph.add_edge(3, 4);
graph.add_edge(4, 5);
graph.add_edge(3, 5);
int result = graph.count_bridges();
return 0;
}
Using depths instead of pre-order labels works because of the following:
Lemma: Every edge in the DFS traversal that doesn't point to the subtree of node \(v\) must point to some direct ancestor of it in the rooted forest.
Proof: Suppose by contradiction we have some edge \(e\) and node \(u\), such that in the DFS traversal node \(u\) is explored when we get to node \(v\), but \(u\) is not a direct ancestor of \(v\). This situation is impossible as our DFS traversal would have traversed edge \(e\) when it was processing node \(u\) and thus node \(v\) would have instead become a direct descentant of \(u\). Thus, it's impossible for the given DFS algorithm to construct a rooted forest that contradicts the lemma.
functions | ||
std::deque::back |
en.cppreference.com | cplusplus.com |
std::deque::push_back |
en.cppreference.com | cplusplus.com |
std::min |
en.cppreference.com | cplusplus.com |
std::vector::operator[] |
en.cppreference.com | cplusplus.com |
std::vector::resize |
en.cppreference.com | cplusplus.com |
classes | ||
std::deque |
en.cppreference.com | cplusplus.com |
std::vector |
en.cppreference.com | cplusplus.com |
Implement the Tarjan's bridge-finding algorithm. Construct some undirected graphs and then use the implemented algorithm to count the number of bridges on it.