Given an undirected connected Graph with V vertices (Numbered from 0 to V-1) and E edges. An edge is represented [ai, bi] denoting that there is an edge from vertex ai to bi . An edge is called a bridge if its removal makes some vertex unable to reach another vertex.
Return all bridges in the graph in any order.
Input: V = 4, E = [ [0,1],[1,2],[2,0],[1,3] ]
Output: [ [1, 3] ]
Explanation: The edge [1, 3] is the critical edge because if we remove the edge the graph will be divided into 2 components.
Input: V = 3, E = [[0,1],[1,2],[2,0]]
Result: []
Explanation: There no bridges in the graph.
Input: V = 2, E = [[0,1]]
Pre Requisite: DFS traversal technique
Any edge in a component of a graph is called a bridge when the component is divided into 2 or more components if that particular edge is removed.
low[neighbor] > tin[current_node]
to determine if the edge is a bridge. Add the bridge to the list of bridges if the condition is met.
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
// To store the current time during traversal
int timer = 1;
/* Helper function to make DFS calls while
checking for bridges in the graph */
void dfs(int node, int parent, vector<int> &vis, vector<int> adj[],
vector<int> &tin, vector<int> &low,
vector<vector<int>> &bridges) {
// Mark the node as visited
vis[node] = 1;
/* Time of insertion and the lowest time of
insert for node will be the current time */
tin[node] = low[node] = timer;
// Increment the current time
timer++;
// Traverse all its neighbors
for (auto it : adj[node]) {
// Skip the parent
if (it == parent) continue;
// If a neighbor is not visited
if (vis[it] == 0) {
// Make a recursive DFS call
dfs(it, node, vis, adj, tin, low, bridges);
/* Once the recursive DFS call returns, upate
the lowest time of insertion for the node */
low[node] = min(low[it], low[node]);
/* If the lowest time of insertion of the
node is found to be greater than the
time of insertion of the neighbor */
if (low[it] > tin[node]) {
// The edge represents a bridge
bridges.push_back({it, node});
}
}
// Else if the neighbor is already visited
else {
// Update the lowest time of insertion of the node
low[node] = min(low[node], low[it]);
}
}
}
public:
// Function to identify the bridges in a graph
vector<vector<int>> criticalConnections(int n,
vector<vector<int>>& connections) {
// Adjacency list
vector<int> adj[n];
// Add all the edges to the adjacency list
for (auto it : connections) {
int u = it[0], v = it[1];
adj[u].push_back(v);
adj[v].push_back(u);
}
// Visited array
vector<int> vis(n, 0);
// To store the time of insertion (discovery time) of nodes
vector<int> tin(n);
// To store the lowest time of insert of the nodes
vector<int> low(n);
// To store the bridges of the graph
vector<vector<int>> bridges;
// Start a DFS traversal from node 0 with its parent as -1
dfs(0, -1, vis, adj, tin, low, bridges);
// Return the computed result
return bridges;
}
};
int main() {
int V = 4;
vector<vector<int>> E = {
{0,1},
{1,2},
{2,0},
{1,3}
};
// Creating an instance of Solution class
Solution obj;
// Function call to identify the bridges in a graph
vector<vector<int>> ans = obj.criticalConnections(V, E);
cout << "The critical connections in the given graph are:\n";
for(int i=0; i < ans.size(); i++) {
cout << ans[i][0] << " " << ans[i][1] << endl;
}
return 0;
}
import java.util.*;
class Solution {
private int timer = 1;
/* Helper function to make DFS calls while
checking for bridges in the graph */
private void dfs(int node, int parent, int[] vis, List<Integer>[] adj,
int[] tin, int[] low, List<List<Integer>> bridges) {
// Mark the node as visited
vis[node] = 1;
/* Time of insertion and the lowest time of
insert for node will be the current time */
tin[node] = low[node] = timer;
// Increment the current time
timer++;
// Traverse all its neighbors
for (int it : adj[node]) {
// Skip the parent
if (it == parent) continue;
// If a neighbor is not visited
if (vis[it] == 0) {
// Make a recursive DFS call
dfs(it, node, vis, adj, tin, low, bridges);
/* Once the recursive DFS call returns, update
the lowest time of insertion for the node */
low[node] = Math.min(low[it], low[node]);
/* If the lowest time of insertion of the
node is found to be greater than the
time of insertion of the neighbor */
if (low[it] > tin[node]) {
// The edge represents a bridge
bridges.add(Arrays.asList(it, node));
}
}
// Else if the neighbor is already visited
else {
// Update the lowest time of insertion of the node
low[node] = Math.min(low[node], low[it]);
}
}
}
// Function to identify the bridges in a graph
public List<List<Integer>> criticalConnections(int n,
List<List<Integer>> connections) {
// Adjacency list
List<Integer>[] adj = new ArrayList[n];
for (int i = 0; i < n; i++) {
adj[i] = new ArrayList<>();
}
// Add all the edges to the adjacency list
for (List<Integer> it : connections) {
int u = it.get(0), v = it.get(1);
adj[u].add(v);
adj[v].add(u);
}
// Visited array
int[] vis = new int[n];
// To store the time of insertion (discovery time) of nodes
int[] tin = new int[n];
// To store the lowest time of insert of the nodes
int[] low = new int[n];
// To store the bridges of the graph
List<List<Integer>> bridges = new ArrayList<>();
// Start a DFS traversal from node 0 with its parent as -1
dfs(0, -1, vis, adj, tin, low, bridges);
// Return the computed result
return bridges;
}
public static void main(String[] args) {
int V = 4;
List<List<Integer>> E = Arrays.asList(
Arrays.asList(0, 1),
Arrays.asList(1, 2),
Arrays.asList(2, 0),
Arrays.asList(1, 3)
);
// Creating an instance of Solution class
Solution obj = new Solution();
// Function call to identify the bridges in a graph
List<List<Integer>> ans = obj.criticalConnections(V, E);
System.out.println("The critical connections in the given graph are:");
for (List<Integer> bridge : ans) {
System.out.println(bridge.get(0) + " " + bridge.get(1));
}
}
}
class Solution:
def __init__(self):
self.timer = 1
def dfs(self, node, parent, vis, adj, tin, low, bridges):
# Mark the node as visited
vis[node] = 1
# Time of insertion and the lowest time of
# insert for node will be the current time
tin[node] = low[node] = self.timer
# Increment the current time
self.timer += 1
# Traverse all its neighbors
for it in adj[node]:
# Skip the parent
if it == parent:
continue
# If a neighbor is not visited
if vis[it] == 0:
# Make a recursive DFS call
self.dfs(it, node, vis, adj, tin, low, bridges)
# Once the recursive DFS call returns, update
# the lowest time of insertion for the node
low[node] = min(low[it], low[node])
# If the lowest time of insertion of the
# node is found to be greater than the
# time of insertion of the neighbor
if low[it] > tin[node]:
# The edge represents a bridge
bridges.append([it, node])
else:
# Update the lowest time of insertion of the node
low[node] = min(low[node], tin[it])
def criticalConnections(self, n, connections):
# Adjacency list
adj = [[] for _ in range(n)]
# Add all the edges to the adjacency list
for u, v in connections:
adj[u].append(v)
adj[v].append(u)
# Visited array
vis = [0] * n
# To store the time of insertion (discovery time) of nodes
tin = [0] * n
# To store the lowest time of insert of the nodes
low = [0] * n
# To store the bridges of the graph
bridges = []
# Start a DFS traversal from node 0 with its parent as -1
self.dfs(0, -1, vis, adj, tin, low, bridges)
# Return the computed result
return bridges
# Main function
if __name__ == "__main__":
V = 4
E = [
[0, 1],
[1, 2],
[2, 0],
[1, 3]
]
# Creating an instance of Solution class
sol = Solution()
# Function call to identify the bridges in a graph
ans = sol.criticalConnections(V, E)
print("The critical connections in the given graph are:")
for bridge in ans:
print(bridge[0], bridge[1])
class Solution {
constructor() {
this.timer = 1;
}
/* Helper function to make DFS calls while
checking for bridges in the graph */
dfs(node, parent, vis, adj, tin, low, bridges) {
// Mark the node as visited
vis[node] = 1;
/* Time of insertion and the lowest time of
insert for node will be the current time */
tin[node] = low[node] = this.timer;
// Increment the current time
this.timer++;
// Traverse all its neighbors
for (let it of adj[node]) {
// Skip the parent
if (it === parent) continue;
// If a neighbor is not visited
if (vis[it] === 0) {
// Make a recursive DFS call
this.dfs(it, node, vis, adj, tin, low, bridges);
/* Once the recursive DFS call returns, update
the lowest time of insertion for the node */
low[node] = Math.min(low[it], low[node]);
/* If the lowest time of insertion of the
node is found to be greater than the time
of insertion of the neighbor */
if (low[it] > tin[node]) {
// The edge represents a bridge
bridges.push([it, node]);
}
} else {
// Update the lowest time of insertion of the node
low[node] = Math.min(low[node], tin[it]);
}
}
}
// Function to identify the bridges in a graph
criticalConnections(n, connections) {
// Adjacency list
const adj = new Array(n).fill(0).map(() => []);
// Add all the edges to the adjacency list
for (let [u, v] of connections) {
adj[u].push(v);
adj[v].push(u);
}
// Visited array
const vis = new Array(n).fill(0);
// To store the time of insertion (discovery time) of nodes
const tin = new Array(n).fill(0);
// To store the lowest time of insert of the nodes
const low = new Array(n).fill(0);
// To store the bridges of the graph
const bridges = [];
// Start a DFS traversal from node 0 with its parent as -1
this.dfs(0, -1, vis, adj, tin, low, bridges);
// Return the computed result
return bridges;
}
}
// Main function
(() => {
const V = 4;
const E = [
[0, 1],
[1, 2],
[2, 0],
[1, 3]
];
// Creating an instance of Solution class
const obj = new Solution();
// Function call to identify the bridges in a graph
const ans = obj.criticalConnections(V, E);
console.log("The critical connections in the given graph are:");
for (let [u, v] of ans) {
console.log(u, v);
}
})();
Time Complexity: O(V+E) (where E represents the number of edges in the graph)
A DFS traversal is performed which takes O(V+E) time.
Space Complexity: O(V) The algorithm uses two arrays to store the discovery time and lowest time of insertion taking O(V) space. The list of bridges returned will take O(E) space in worst-case when all the edges are bridges in the graph.
Q: Why not use simple DFS to find bridges?
A: A naive approach would remove each edge one by one and check connectivity (O(E * (V + E))), which is too slow. Tarjan’s Algorithm finds all bridges in O(V + E) efficiently.
Q: Why use low[] instead of just tracking parent-child relationships?
A: low[] helps identify whether a subtree has an alternate back edge, preventing false bridge detection.
Q: What if the graph had duplicate edges?
A: Bridges exist in simple graphs; parallel edges mean removing one does not disconnect the graph.
Q: Can we modify the algorithm to find articulation points (cut vertices)?
A: Yes! Articulation points are found using a similar DFS approach with low[] values.