Given an undirected graph represented by an adjacency list adj[][], where adj[i] contains all the nodes adjacent to vertex i, we need to find all the bridges present in the graph.
- An edge in an undirected connected graph is called a Bridge if removing it increases the number of connected components (i.e., disconnects the graph).
- For a disconnected undirected graph, a bridge is still an edge whose removal increases the number of disconnected components.
Examples:
Input:Â adj[][]= [[1, 2, 3], [0, 2], [0, 1], [0, 4], [3]]
Output: [[0, 3], [3, 4]]
Explanation: If we disconnect the edges (0, 3) or (3, 4), the graph gets divided into multiple disconnected components. Hence, these edges are bridges.Input: adj[][] =[[1], [2], [3], []]
Output: [[0, 1], [1, 2], [2, 3]]
Explanation: If we disconnect the edges (0, 1) or (1, 2) or (3, 4), the graph gets divided into multiple disconnected components. Hence, these edges are bridges.
Table of Content
[Naive Approach] Remove Edges One by One - O(e*(v+e)) Time and O(v+e) Space
The idea is to check if an edge is a bridge by temporarily removing it and testing connectivity.
We perform a DFS excluding the current edge and observe if all vertices remain reachable or not. If any vertex becomes unreachable, then that edge is a bridge, as its removal increases components.
//Driver Code Starts
#include <iostream>
#include <vector>
using namespace std;
//Driver Code Ends
// DFS utility to check connectivity after removing an edge
void dfs(int u, vector<vector<int>>& adj, vector<bool>& visited,
int skipU, int skipV) {
visited[u] = true;
for (int v : adj[u]) {
// Skip the edge we are testing
if ((u == skipU && v == skipV) ||
(u == skipV && v == skipU)) {
continue;
}
if (!visited[v]) {
dfs(v, adj, visited, skipU, skipV);
}
}
}
// Check if removing edge (u, v) disconnects its endpoints
bool isBridge(int u, int v, vector<vector<int>>& adj, int vCount) {
vector<bool> visited(vCount, false);
// Start DFS from one endpoint of the edge
dfs(u, adj, visited, u, v);
// If the other endpoint is unreachable, the edge is a bridge
return !visited[v];
}
// Function to find all bridges
vector<vector<int>> findBridges(vector<vector<int>>& adj) {
vector<vector<int>> bridges;
int v = adj.size();
// Check each edge
for (int u = 0; u < v; u++) {
for (int w : adj[u]) {
// Avoid duplicate checking
if (u < w) {
if (isBridge(u, w, adj, v)) {
bridges.push_back({u, w});
}
}
}
}
return bridges;
}
int main() {
vector<vector<int>> adj = {{1, 2, 3},{0, 2},{0, 1}, {0, 4},{3}};
//Driver Code Starts
vector<vector<int>> res = findBridges(adj);
for (auto bridge : res) {
cout << "[" << bridge[0] << ", " << bridge[1] << "] ";
}
return 0;
}
//Driver Code Ends
//Driver Code Starts
import java.util.ArrayList;
class GFG {
//Driver Code Ends
// DFS utility to check connectivity after removing an edge
static void dfs(int u, ArrayList<ArrayList<Integer>> adj, boolean[] visited,
int skipU, int skipV) {
visited[u] = true;
for (int v : adj.get(u)) {
// Skip the edge we are testing
if ((u == skipU && v == skipV) || (u == skipV && v == skipU)) {
continue;
}
if (!visited[v]) {
dfs(v, adj, visited, skipU, skipV);
}
}
}
// Check if removing edge (u, v) disconnects the graph
static boolean isBridge(int u, int v, ArrayList<ArrayList<Integer>> adj, int vCount) {
boolean[] visited = new boolean[vCount];
// Start DFS from one endpoint of the edge
dfs(u, adj, visited, u, v);
return !visited[v];
}
// Function to find all bridges
static ArrayList<ArrayList<Integer>> findBridges(ArrayList<ArrayList<Integer>> adj) {
ArrayList<ArrayList<Integer>> bridges = new ArrayList<>();
int v = adj.size();
// Check each edge (u, w)
for (int u = 0; u < v; u++) {
for (int w : adj.get(u)) {
// To avoid duplicate checking
if (u < w) {
if (isBridge(u, w, adj, v)) {
ArrayList<Integer> bridge = new ArrayList<>();
bridge.add(u);
bridge.add(w);
bridges.add(bridge);
}
}
}
}
return bridges;
}
// Helper function to add an edge
static void addEdge(ArrayList<ArrayList<Integer>> adj, int u, int v) {
//Driver Code Starts
adj.get(u).add(v);
adj.get(v).add(u);
}
public static void main(String[] args) {
int v = 5;
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
for (int i = 0; i < v; i++) {
adj.add(new ArrayList<>());
}
// Add edges
addEdge(adj, 0, 1);
addEdge(adj, 0, 2);
addEdge(adj, 0, 3);
addEdge(adj, 1, 2);
addEdge(adj, 3, 4);
ArrayList<ArrayList<Integer>> res = findBridges(adj);
for (ArrayList<Integer> bridge : res) {
System.out.print(bridge + " ");
}
}
}
//Driver Code Ends
# DFS utility to check connectivity after removing an edge
def dfs(u, adj, visited, skipU, skipV):
visited[u] = True
for v in adj[u]:
# Skip the edge we are testing
if (u == skipU and v == skipV) or (u == skipV and v == skipU):
continue
if not visited[v]:
dfs(v, adj, visited, skipU, skipV)
# Check if removing edge (u, v) disconnects the graph
def isBridge(u, v, adj, vCount):
visited = [False] * vCount
# Start DFS from a different node
start = 0 if u != 0 else 1
dfs(start, adj, visited, u, v)
for i in range(vCount):
if not visited[i]:
return True
return False
# Function to find all bridges
def findBridges(adj):
bridges = []
v = len(adj)
# Check each edge (u, v)
for u in range(v):
for w in adj[u]:
# To avoid duplicate checking
if u < w:
if isBridge(u, w, adj, v):
bridges.append([u, w])
return bridges
#Driver Code Starts
if __name__ == "__main__":
# Given adjacency list
adj = [[1, 2, 3], [0, 2], [0, 1], [0, 4], [3]]
res = findBridges(adj)
for bridge in res:
print(f"[{bridge[0]}, {bridge[1]}]", end=" ")
#Driver Code Ends
//Driver Code Starts
using System;
using System.Collections.Generic;
class GFG
{
//Driver Code Ends
// DFS utility to check connectivity after removing an edge
static void dfs(int u, List<List<int>> adj, bool[] visited, int skipU, int skipV)
{
visited[u] = true;
foreach (int v in adj[u])
{
// Skip the edge we are testing
if ((u == skipU && v == skipV) || (u == skipV && v == skipU))
continue;
if (!visited[v])
dfs(v, adj, visited, skipU, skipV);
}
}
// Check if removing edge (u, v) disconnects its endpoints
static bool isBridge(int u, int v, List<List<int>> adj, int vCount)
{
bool[] visited = new bool[vCount];
// Start DFS from one endpoint of the edge
dfs(u, adj, visited, u, v);
// If the other endpoint is unreachable, the edge is a bridge
return !visited[v];
}
// Function to find all bridges
static List<List<int>> findBridges(List<List<int>> adj)
{
List<List<int>> bridges = new List<List<int>>();
int v = adj.Count;
// Check each edge
for (int u = 0; u < v; u++)
{
foreach (int w in adj[u])
{
// Avoid duplicate checking
if (u < w)
{
if (isBridge(u, w, adj, v))
{
bridges.Add(new List<int> { u, w });
}
}
}
}
return bridges;
}
// Helper function to add an edge
static void addEdge(List<List<int>> adj, int u, int v)
{
adj[u].Add(v);
adj[v].Add(u);
//Driver Code Starts
}
static void Main()
{
int v = 5;
List<List<int>> adj = new List<List<int>>();
for (int i = 0; i < v; i++)
adj.Add(new List<int>());
// Add edges
addEdge(adj, 0, 1);
addEdge(adj, 0, 2);
addEdge(adj, 0, 3);
addEdge(adj, 1, 2);
addEdge(adj, 3, 4);
List<List<int>> res = findBridges(adj);
foreach (var bridge in res)
{
Console.Write($"[{bridge[0]}, {bridge[1]}] ");
}
}
}
//Driver Code Ends
// DFS utility to check connectivity after removing an edge
function dfs(u, adj, visited, skipU, skipV) {
visited[u] = true;
for (let v of adj[u]) {
// Skip the edge we are testing
if ((u === skipU && v === skipV) ||
(u === skipV && v === skipU)) {
continue;
}
if (!visited[v]) {
dfs(v, adj, visited, skipU, skipV);
}
}
}
// Check if removing edge (u, v) disconnects the graph
function isBridge(u, v, adj, vCount) {
const visited = new Array(vCount).fill(false);
// Start DFS from one endpoint of the edge
dfs(u, adj, visited, u, v);
// If the other endpoint is unreachable, the edge is a bridge
return !visited[v];
}
// Function to find all bridges
function findBridges(adj) {
const bridges = [];
const v = adj.length;
// Check each edge
for (let u = 0; u < v; u++) {
for (let w of adj[u]) {
// Avoid duplicate checking
if (u < w) {
if (isBridge(u, w, adj, v)) {
bridges.push([u, w]);
}
}
}
}
return bridges;
}
// Driver code
const adj = [[1, 2, 3], [0, 2], [0, 1], [0, 4], [3]];
const res = findBridges(adj);
for (const bridge of res) {
process.stdout.write(`[${bridge[0]}, ${bridge[1]}] `);
}
Output
[0, 3] [3, 4]
Time Complexity: O(E * (V + E)), due to DFS for each edge in edgeList where E and V denote number of edges and vertices respectively.
Space Complexity: O(V + E), due to space used for adjacency list and visited array
[Expected Approach] Tarjan's Algorithm - O(V+E) Time and O(V) Space
The goal is to detect bridges in one single DFS traversal - without repeatedly removing edges. If a node v cannot reach any ancestor of its parent node u in the DFS tree, then the edge (u, v) is a bridge. Tarjan's algorithm uses two additional timestamps to implement this along with visited[] that we use in normal DFS.
- disc[u] -> The discovery time of node u during DFS (the time when we first visit it).
- low[u] -> The earliest discovery time reachable from u, either through its descendants or via back edges.
We perform a Depth-First Search (DFS) with tracking of parent nodes. This helps us differentiate between a tree edge (edge to a child in DFS) and a back edge (edge to an already visited ancestor).
For every adjacent node v of a node u, we handle three cases:
- If v is the parent of u: We skip this edge.
It represents the connection we just came from and isn’t needed in further processing.- If v is already visited: This indicates a back edge from u to an ancestor v.
Update low[u] = min(low[u], disc[v]). This captures the earliest discovery time reachable from u via a back edge.- If v is not visited: Recurse with DFS on v to explore further.
After returning, update low[u] = min(low[u], low[v]). This reflects the earliest node reachable from v's subtree.
Now check if low[v] > disc[u]. This means the subtree rooted at v cannot reach any ancestor of u, making (u, v) a bridge.
//Driver Code Starts
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//Driver Code Ends
void dfs(int u, int parent, vector<vector<int>>& adj, vector<bool>& visited,
vector<int>& disc, vector<int>& low, int& timer,
vector<vector<int>>& bridges) {
visited[u] = true;
// Initialize discovery and low time
disc[u] = low[u] = timer++;
// Explore all adjacent nodes
for (int v : adj[u]) {
// Skip the edge to parent node
if (v == parent)
continue;
// If v is not visited, recurse for it
if (!visited[v]) {
dfs(v, u, adj, visited, disc, low, timer, bridges);
// Update low value of u
low[u] = min(low[u], low[v]);
// If low[v] > disc[u], edge (u, v) is a bridge
if (low[v] > disc[u]) {
bridges.push_back({u, v});
}
}
else {
// Update low[u] for back edge
low[u] = min(low[u], disc[v]);
}
}
}
// Function to find all bridges (critical connections)
vector<vector<int>> findBridges(vector<vector<int>>& adj) {
int vCount = adj.size();
vector<bool> visited(vCount, false);
// Discovery time of nodes
vector<int> disc(vCount, -1);
// Lowest reachable discovery time
vector<int> low(vCount, -1);
vector<vector<int>> bridges;
int timer = 0;
// Run DFS for each unvisited node
for (int i = 0; i < vCount; i++) {
if (!visited[i]) {
dfs(i, -1, adj, visited, disc, low, timer, bridges);
}
}
return bridges;
}
//Driver Code Starts
int main() {
// Given adjacency list
vector<vector<int>> adj = {{1, 2, 3},{0, 2},{0, 1},{0, 4},{3}};
vector<vector<int>> res = findBridges(adj);
for (auto bridge : res) {
cout << "[" << bridge[0] << ", " << bridge[1] << "] ";
}
return 0;
}
//Driver Code Ends
//Driver Code Starts
import java.util.ArrayList;
import java.util.Arrays;
public class GFG {
//Driver Code Ends
static void dfs(int u, int parent, ArrayList<ArrayList<Integer>> adj, boolean[] visited,
int[] disc, int[] low, int[] timer, ArrayList<ArrayList<Integer>> bridges) {
visited[u] = true;
// Initialize discovery and low time
disc[u] = low[u] = timer[0]++;
// Explore all adjacent nodes
for (int v : adj.get(u)) {
// Skip the edge to parent node
if (v == parent)
continue;
// If v is not visited, recurse for it
if (!visited[v]) {
dfs(v, u, adj, visited, disc, low, timer, bridges);
// Update low value of u
low[u] = Math.min(low[u], low[v]);
// If low[v] > disc[u], edge (u, v) is a bridge
if (low[v] > disc[u]) {
ArrayList<Integer> bridge = new ArrayList<>();
bridge.add(u);
bridge.add(v);
bridges.add(bridge);
}
} else {
// Update low[u] for back edge
low[u] = Math.min(low[u], disc[v]);
}
}
}
// Function to find all bridges (critical connections)
static ArrayList<ArrayList<Integer>> findBridges(ArrayList<ArrayList<Integer>> adj) {
int vCount = adj.size();
boolean[] visited = new boolean[vCount];
// Discovery time of nodes
int[] disc = new int[vCount];
// Lowest reachable discovery time
int[] low = new int[vCount];
Arrays.fill(disc, -1);
Arrays.fill(low, -1);
ArrayList<ArrayList<Integer>> bridges = new ArrayList<>();
int[] timer = {0};
// Run DFS for each unvisited node
for (int i = 0; i < vCount; i++) {
if (!visited[i]) {
dfs(i, -1, adj, visited, disc, low, timer, bridges);
}
}
return bridges;
}
//Driver Code Starts
// Function to add an undirected edge
static void addEdge(ArrayList<ArrayList<Integer>> adj, int u, int v) {
adj.get(u).add(v);
adj.get(v).add(u);
}
public static void main(String[] args) {
int v = 5;
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
for (int i = 0; i < v; i++) {
adj.add(new ArrayList<>());
}
addEdge(adj, 0, 1);
addEdge(adj, 0, 2);
addEdge(adj, 0, 3);
addEdge(adj, 1, 2);
addEdge(adj, 3, 4);
ArrayList<ArrayList<Integer>> res = findBridges(adj);
for (ArrayList<Integer> bridge : res) {
System.out.print("[" + bridge.get(0) + ", " + bridge.get(1) + "] ");
}
}
}
//Driver Code Ends
def dfs(u, parent, adj, visited, disc, low, timer, bridges):
visited[u] = True
# Initialize discovery and low time
disc[u] = low[u] = timer[0]
timer[0] += 1
# Explore all adjacent nodes
for v in adj[u]:
# Skip the edge to parent node
if v == parent:
continue
# If v is not visited, recurse for it
if not visited[v]:
dfs(v, u, adj, visited, disc, low, timer, bridges)
# Update low value of u
low[u] = min(low[u], low[v])
# If low[v] > disc[u], edge (u, v) is a bridge
if low[v] > disc[u]:
bridges.append([u, v])
else:
# Update low[u] for back edge
low[u] = min(low[u], disc[v])
# Function to find all bridges (critical connections)
def findBridges(adj):
vCount = len(adj)
visited = [False] * vCount
# Discovery time of nodes
disc = [-1] * vCount
# Lowest reachable discovery time
low = [-1] * vCount
bridges = []
timer = [0]
# Run DFS for each unvisited node
for i in range(vCount):
if not visited[i]:
dfs(i, -1, adj, visited, disc, low, timer, bridges)
return bridges
#Driver Code Starts
if __name__ == "__main__":
adj = [[1, 2, 3], [0, 2], [0, 1], [0, 4], [3]]
res = findBridges(adj)
for bridge in res:
print(f"[{bridge[0]}, {bridge[1]}]", end=" ")
#Driver Code Ends
//Driver Code Starts
using System;
using System.Collections.Generic;
public class GFG
{
//Driver Code Ends
static void dfs(int u, int parent, List<List<int>> adj, List<bool> visited,
List<int> disc, List<int> low, ref int timer,
List<List<int>> bridges)
{
visited[u] = true;
// Initialize discovery and low time
disc[u] = low[u] = timer++;
// Explore all adjacent nodes
foreach (int v in adj[u])
{
// Skip the edge to parent node
if (v == parent)
continue;
// If v is not visited, recurse for it
if (!visited[v])
{
dfs(v, u, adj, visited, disc, low, ref timer, bridges);
// Update low value of u
low[u] = Math.Min(low[u], low[v]);
// If low[v] > disc[u], edge (u, v) is a bridge
if (low[v] > disc[u])
{
bridges.Add(new List<int> { u, v });
}
}
else
{
// Update low[u] for back edge
low[u] = Math.Min(low[u], disc[v]);
}
}
}
//Driver Code Starts
// Function to add an undirected edge
static void addEdge(List<List<int>> adj, int u, int v)
{
adj[u].Add(v);
adj[v].Add(u);
}
// Function to find all bridges (critical connections)
static List<List<int>> findBridges(List<List<int>> adj)
{
int vCount = adj.Count;
List<bool> visited = new List<bool>(new bool[vCount]);
// Discovery time of nodes
List<int> disc = new List<int>(new int[vCount]);
// Lowest reachable discovery time
List<int> low = new List<int>(new int[vCount]);
for (int i = 0; i < vCount; i++)
{
disc[i] = -1;
low[i] = -1;
}
List<List<int>> bridges = new List<List<int>>();
int timer = 0;
// Run DFS for each unvisited node
for (int i = 0; i < vCount; i++)
{
if (!visited[i])
{
dfs(i, -1, adj, visited, disc, low, ref timer, bridges);
}
}
return bridges;
}
public static void main()
{
int v = 5;
List<List<int>> adj = new List<List<int>>();
for (int i = 0; i < v; i++)
{
adj.Add(new List<int>());
}
addEdge(adj, 0, 1);
addEdge(adj, 0, 2);
addEdge(adj, 0, 3);
addEdge(adj, 1, 2);
addEdge(adj, 3, 4);
List<List<int>> res = findBridges(adj);
foreach (List<int> bridge in res)
{
Console.Write($"[{bridge[0]}, {bridge[1]}] ");
}
}
}
//Driver Code Ends
function dfs(u, parent, adj, visited, disc, low, timer, bridges) {
visited[u] = true;
// Initialize discovery and low time
disc[u] = low[u] = timer.value++;
// Explore all adjacent nodes
for (let v of adj[u]) {
// Skip the edge to parent node
if (v === parent)
continue;
// If v is not visited, recurse for it
if (!visited[v]) {
dfs(v, u, adj, visited, disc, low, timer, bridges);
// Update low value of u
low[u] = Math.min(low[u], low[v]);
// If low[v] > disc[u], edge (u, v) is a bridge
if (low[v] > disc[u]) {
bridges.push([u, v]);
}
} else {
// Update low[u] for back edge
low[u] = Math.min(low[u], disc[v]);
}
}
}
// Function to find all bridges (critical connections)
function findBridges(adj) {
let vCount = adj.length;
let visited = new Array(vCount).fill(false);
// Discovery time of nodes
let disc = new Array(vCount).fill(-1);
// Lowest reachable discovery time
let low = new Array(vCount).fill(-1);
let bridges = [];
let timer = { value: 0 };
// Run DFS for each unvisited node
for (let i = 0; i < vCount; i++) {
if (!visited[i]) {
dfs(i, -1, adj, visited, disc, low, timer, bridges);
}
}
return bridges;
}
//Driver Code Starts
//Driver Code
// Given adjacency list
let adj = [[1, 2, 3], [0, 2], [0, 1], [0, 4], [3]];
let res = findBridges(adj);
for (let bridge of res) {
process.stdout.write(`[${bridge[0]}, ${bridge[1]}] `);
}
//Driver Code Ends
Output
[3, 4] [0, 3]

