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)
, totaln
nodes, totalm
edges. - 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 nodenode
was first visited during the DFS traversal.low[node]
(Lowest Ancestor Time): The lowest discovery time reachable fromnode
(includingnode
itself) 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)
, addv
toadjList[u]
andu
toadjList[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
i
from0
ton-1
. - If
!visited[i]
, it meansi
is part of an unvisited component. Start DFS fromi
:dfs(i, parent, timer, disc, low, result, adjList, visited);
- Loop through all nodes
-
Return: The
result
vector containing all bridge edges.
B. dfs
Function (Recursive Traversal)
-
Parameters:
node
(current node),parent
(parent ofnode
in 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
neigh
inadjList[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
neigh
is 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 meansnode
can reach whateverneigh
can 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 tonode
or 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
neigh
IS 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 thatnode
can reach an already visited node (neigh
) directly, which might be an ancestor and thus part of a cycle.disc[neigh]
is used becauseneigh
is an ancestor, and itsdisc
value 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
,visited
arrays:O(V)
.- Recursion stack for DFS:
O(V)
in worst case (skewed graph). result
vector:O(E)
in worst case (a tree hasV-1
bridges).
5. Key Points / Intuition
disc[u]
tells us “whenu
was first discovered”.low[u]
tells us “the highest point (lowest discovery time) thatu
or any node inu
’s DFS subtree can reach using at most one back-edge”.- If
low[v] > disc[u]
for an edge(u, v)
wherev
is a child ofu
in the DFS tree, it meansv
and its entire subtree have no way to reachu
or any ofu
’s ancestors except through the edge(u, v)
itself. Hence,(u, v)
is a bridge.