Algoteka
Log in to submit your own samples!

Tarjan's Bridge-finding Algorithm

Problem by oml1111
# Tech tags Title Creator Created date
1 0
2022-11-21 11:38
View all samples for this language (1 verified and 0 unverified)

O(n+m) Readable simplified solution | C++ |

By oml1111 |
0 likes

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.

Code

#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;
}

Further reading

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.

Finding bridges in a graph - cp-algorithms.com

References

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

Problem Description

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.

Tarjan's bridge-finding algorithm - wikipedia.org

View sample discussion (0 comments)
View problem discussion (0 comments)