Bridges In Graph
#include <bits/stdc++.h> // Includes common standard libraries like iostream, vector, unordered_map, list, algorithm, etc.using namespace std; // Use the standard namespace.
// Recursive DFS function to find bridges.// 'node': Current node being visited.// 'parent': Parent of the current node in the DFS tree (used to avoid going back immediately).// 'timer': Global time counter for discovery and low links. Passed by reference to increment globally.// 'disc': Discovery time array (disc[i] = time when node i was first visited).// 'low': Low-link value array (low[i] = lowest discovery time reachable from node i or its subtree,// including back-edges to ancestors).// 'result': Vector to store the identified bridge edges.// 'adjList': Adjacency list of the graph.// 'visited': Boolean array to keep track of visited nodes.void dfs(int node, int parent, int &timer, vector<int> &disc, vector<int> &low, vector<vector<int>> &result, unordered_map<int, list<int>> &adjList, vector<bool> &visited) {
visited[node] = true; // Mark current node as visited. disc[node] = low[node] = timer++; // Set discovery time and initial low-link value, then increment timer.
// Iterate over all neighbors of the current node. for(auto neigh : adjList[node]) { // If the neighbor is the immediate parent, skip it. This prevents considering the edge // from child back to parent as a back-edge that forms a cycle in the DFS tree. if(neigh == parent) { continue; }
// Case 1: Neighbor is not visited. It's a forward edge to a child in the DFS tree. if(visited[neigh] == false) { // Recursively call DFS on the neighbor. 'node' becomes the parent for 'neigh'. dfs(neigh, node, timer, disc, low, result, adjList, visited);
// After the recursive call returns (meaning 'neigh' and its subtree are processed): // Update the low-link value of the current node. // 'low[node]' is the minimum of its current 'low' value and 'low[neigh]'. // This means 'node' can reach whatever 'neigh' (and its subtree) can reach. low[node] = min(low[node], low[neigh]);
// Bridge Condition Check: // If the lowest discovery time reachable from 'neigh' (or its subtree) is greater // than the discovery time of the current node 'node', it means there is no back-edge // from 'neigh's subtree to 'node' or any of 'node's ancestors. // Therefore, the edge (node, neigh) is a bridge. if(low[neigh] > disc[node]) { result.push_back({node, neigh}); // Add this edge to the list of bridges. } } else { // Case 2: Neighbor is visited and is NOT the parent. It's a back-edge (or cross-edge). // This means a cycle is detected. Update the low-link value of the current node. // 'low[node]' should be the minimum of its current 'low' value and the discovery time // of the visited neighbor (which is an ancestor or part of a cycle). low[node] = min(low[node], disc[neigh]); } }}
// Main function to find all bridges in an undirected graph.// 'edges': List of edges in the graph. Each inner vector {u, v} represents an edge.// 'n': Total number of nodes.// 'm': Total number of edges.// Returns a vector of vectors, where each inner vector {u, v} is a bridge edge.vector<vector<int>> findBridges(vector<vector<int>> &edges, int n, int m) { // 1. Create Adjacency List for the graph. unordered_map<int, list<int>> adjList; for(int i = 0; i < m; i++) { int u = edges[i][0]; int v = edges[i][1]; adjList[u].push_back(v); // Add edge u -- v. adjList[v].push_back(u); // Add edge v -- u (for undirected graph). }
// 2. Initialize data structures for DFS and bridge finding. int timer = 0; // Global timer to assign discovery times. vector<int> disc(n, -1); // Discovery time array, initialized to -1. vector<int> low(n, -1); // Low-link value array, initialized to -1. int parent = -1; // Initial parent for the DFS root is -1. vector<bool> visited(n, false); // Visited array, initialized to false. vector<vector<int>> result; // Vector to store the identified bridge edges.
// 3. Iterate through all nodes to ensure all connected components are visited. for(int i = 0; i < n; i++) { // If a node is not visited, it's the root of a new DFS tree in a component. if(!visited[i]) { dfs(i, parent, timer, disc, low, result, adjList, visited); } }
return result; // Return the list of all bridge edges.}
// Main function for input/output.int main() { vector<vector<int>> edges; // Stores input edges as {u, v}. int n, m; // 'n' for nodes, 'm' for edges.
cout << "Enter the number of nodes : "; cin >> n;
cout << "Enter the number of edges : "; cin >> m;
cout << "Enter the edges (u v):" << endl; for(int i = 0; i < m; i++) { int u, v; cin >> u >> v; // Read u and v. edges.push_back({u, v}); // Add edge to vector. }
// Call findBridges function. vector<vector<int>> answer_bridges = findBridges(edges, n, m);
cout << "Bridge Edges : "; if (answer_bridges.empty()) { cout << "None"; // If no bridges found. } else { for(const auto& bridge_edge : answer_bridges) { // Use const auto& for efficient iteration. cout << "[" << bridge_edge[0] << "," << bridge_edge[1] << "] "; } } cout << endl;
return 0; // Indicate successful execution.}1. Problem Statement
- Goal: Identify all “bridges” (also known as “cut edges”) in a given undirected graph.
- Bridge Definition: An edge in a connected graph whose removal increases the number of connected components. In other words, an edge is a bridge if its removal disconnects the graph (or its component).
- Input: List of
edges (u, v), totalnnodes, totalmedges. - Output: A list of pairs, where each pair
[u, v]represents a bridge edge.
2. Core Idea (Tarjan’s Algorithm / DFS-based)
The algorithm uses a Depth-First Search (DFS) traversal and maintains two important arrays for each node:
disc[node](Discovery Time): The time (or order) at which a nodenodewas first visited during the DFS traversal.low[node](Lowest Ancestor Time): The lowest discovery time reachable fromnode(includingnodeitself) through any path, including back-edges (edges to already visited ancestors).
The Bridge Condition: An edge (u, v) (where v is a child of u in the DFS tree) is a bridge if and only if low[v] > disc[u].
This condition means that the subtree rooted at v has no back-edge to u or any ancestor of u. Therefore, removing the edge (u, v) would disconnect v and its subtree from u and the rest of the graph.
3. Algorithm Steps (findBridges and dfs functions)
A. findBridges Function (Main Wrapper)
-
Build Adjacency List:
- Create an
adjList(unordered_map<int, list<int>>) from the inputedges. - For each edge
(u, v), addvtoadjList[u]andutoadjList[v](undirected graph).
- Create an
-
Initialize Data Structures:
timer = 0: A global counter to assign discovery times.disc[n]:vector<int>initialized to-1. Stores discovery times.low[n]:vector<int>initialized to-1. Stores lowest reachable ancestor times.parent = -1: Special value for the parent of the initial DFS call’s root.visited[n]:vector<bool>initialized tofalse. To track visited nodes.result:vector<vector<int>>to store the identified bridge edges.
-
Initiate DFS for all components:
- Loop through all nodes
ifrom0ton-1. - If
!visited[i], it meansiis part of an unvisited component. Start DFS fromi:dfs(i, parent, timer, disc, low, result, adjList, visited);
- Loop through all nodes
-
Return: The
resultvector containing all bridge edges.
B. dfs Function (Recursive Traversal)
-
Parameters:
node(current node),parent(parent ofnodein DFS tree),timer,disc,low,result,adjList,visited. -
Base Actions (on entering node):
visited[node] = true;(Mark current node as visited).disc[node] = low[node] = timer++;(Set discovery and low times, then increment timer).
-
Explore Neighbors: For each
neighinadjList[node]:-
Skip Parent:
if (neigh == parent) continue;(Don’t process the edge back to the immediate parent, as it’s not a true back-edge for cycle detection fromneigh’s perspective). -
If
neighis NOT visited (visited[neigh] == false): (Forward edge to an unvisited child)- Recursively call
dfs(neigh, node, timer, disc, low, result, adjList, visited); - After recursive call returns (from child
neigh):low[node] = min(low[node], low[neigh]);(Updatelow[node]withlow[neigh]. This meansnodecan reach whateverneighcan reach).- Bridge Check:
if (low[neigh] > disc[node]):- This is the critical condition. If
low[neigh](lowest reachable discovery time fromneigh’s subtree) is greater thandisc[node](discovery time of its parentnode), it means there’s no back-edge fromneigh’s subtree tonodeor any ancestor ofnode. Thus,(node, neigh)is a bridge. result.push_back({node, neigh});(Add this bridge to the result).
- This is the critical condition. If
- Recursively call
-
If
neighIS visited (visited[neigh] == true): (Back edge to an already visited ancestor, or cross edge)low[node] = min(low[node], disc[neigh]);(Updatelow[node]withdisc[neigh]. This indicates thatnodecan reach an already visited node (neigh) directly, which might be an ancestor and thus part of a cycle.disc[neigh]is used becauseneighis an ancestor, and itsdiscvalue is its earliest time).
-
4. Time and Space Complexity
- Time Complexity:
O(V + E)- Building
adjList:O(M). - DFS traversal: Each vertex and edge is visited exactly once.
O(V + E). - Total:
O(V + E).
- Building
- Space Complexity:
O(V + E)adjList:O(V + E).disc,low,visitedarrays:O(V).- Recursion stack for DFS:
O(V)in worst case (skewed graph). resultvector:O(E)in worst case (a tree hasV-1bridges).
5. Key Points / Intuition
disc[u]tells us “whenuwas first discovered”.low[u]tells us “the highest point (lowest discovery time) thatuor any node inu’s DFS subtree can reach using at most one back-edge”.- If
low[v] > disc[u]for an edge(u, v)wherevis a child ofuin the DFS tree, it meansvand its entire subtree have no way to reachuor any ofu’s ancestors except through the edge(u, v)itself. Hence,(u, v)is a bridge.