Given an undirected graph with V vertices numbered from 0 to V - 1 and E edges, represented by a 2D array edges[][], where edges[i] = [u, v] denotes an edge between vertices u and v, find if the graph is Biconnected.
A graph is considered Biconnected if:
- The graph is connected, i.e., every vertex is reachable from every other vertex.
- The graph remains connected after the removal of any single vertex.
Input: V = 2, edges[][] = [[0, 1]]
Output: true
Explanation: Removing either vertex leaves the graph with a single vertex, which is considered connected.Input: V = 5, edges[][] = [[1, 0], [1, 2], [0, 2], [0, 3], [3, 4]]
Output: false
Explanation: Removing vertex 3 disconnects vertex 4 from the rest of the graph. Hence, the graph is not biconnected.Input: V = 5, edges[][] = [[1, 0], [1, 2], [0, 2], [0, 3], [3, 4], [2, 4]]
Output: true
Explanation: Removing any single vertex does not disconnect the remaining graph.
Table of Content
[Naive Approach] - Remove Each Vertex and Check Connectivity - O(V Ã (V + E)) Time and O(V) Space
The idea is to remove each vertex one by one and check whether all the remaining vertices are still connected. For every vertex u, we temporarily ignore it and perform a DFS from any other vertex. If some remaining vertex cannot be reached, then removing u disconnects the graph, then the graph is not biconnected. If the graph remains connected after removing every vertex, then it is biconnected.
#include <iostream>
#include <vector>
using namespace std;
void dfs(int u, int removed, vector<vector<int>> &adj,
vector<bool> &vis) {
vis[u] = true;
// Recur for all the vertices adjacent to this vertex.
for (int v : adj[u]) {
// If an adjacent vertex is not removed and
// not visited, then recur for that adjacent.
if (v != removed && !vis[v]) {
dfs(v, removed, adj, vis);
}
}
}
// Returns true if the graph
// is biconnected, else false.
bool isBiconnected(int V, vector<vector<int>> &adj) {
// Remove each vertex and check
// whether the graph remains connected.
for (int removed = 0; removed < V; removed++) {
vector<bool> vis(V, false);
int start = -1;
// Find a starting vertex other
// than the removed vertex.
for (int i = 0; i < V; i++) {
if (i != removed) {
start = i;
break;
}
}
dfs(start, removed, adj, vis);
// Check if all remaining vertices
// are reachable or not.
for (int i = 0; i < V; i++) {
if (i != removed && !vis[i])
return false;
}
}
return true;
}
int main() {
int V = 5;
vector<vector<int>> adj = {
{1, 2, 3},
{0, 2},
{1, 0, 4},
{0, 4},
{3, 2}
};
isBiconnected(V, adj) ? cout << "true" : cout << "false";
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
class GFG {
static void dfs(int u, int removed, ArrayList<ArrayList<Integer>> adj, boolean[] vis) {
vis[u] = true;
// Recur for all the vertices adjacent to this vertex.
for (int v : adj.get(u)) {
// If an adjacent vertex is not removed and
// not visited, then recur for that adjacent.
if (v != removed && !vis[v]) {
dfs(v, removed, adj, vis);
}
}
}
// Returns true if the graph
// is biconnected, else false.
static boolean isBiconnected(int V,
ArrayList<ArrayList<Integer>> adj) {
// Remove each vertex and check
// whether the graph remains connected.
for (int removed = 0; removed < V; removed++) {
boolean[] vis = new boolean[V];
int start = -1;
// Find a starting vertex other
// than the removed vertex.
for (int i = 0; i < V; i++) {
if (i != removed) {
start = i;
break;
}
}
dfs(start, removed, adj, vis);
// Check if all remaining vertices
// are reachable or not.
for (int i = 0; i < V; i++) {
if (i != removed && !vis[i])
return false;
}
}
return true;
}
public static void main(String[] args) {
int V = 5;
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
adj.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
adj.add(new ArrayList<>(Arrays.asList(0, 2)));
adj.add(new ArrayList<>(Arrays.asList(1, 0, 4)));
adj.add(new ArrayList<>(Arrays.asList(0, 4)));
adj.add(new ArrayList<>(Arrays.asList(3, 2)));
System.out.println(isBiconnected(V, adj));
}
}
def dfs(u, removed, adj, vis):
vis[u] = True
# Recur for all the vertices adjacent to this vertex.
for v in adj[u]:
# If an adjacent vertex is not removed and
# not visited, then recur for that adjacent.
if v != removed and not vis[v]:
dfs(v, removed, adj, vis)
# Returns true if the graph
# is biconnected, else false.
def isBiconnected(V, adj):
# Remove each vertex and check
# whether the graph remains connected.
for removed in range(V):
vis = [False] * V
start = -1
# Find a starting vertex other
# than the removed vertex.
for i in range(V):
if i != removed:
start = i
break
dfs(start, removed, adj, vis)
# Check if all remaining vertices
# are reachable or not.
for i in range(V):
if i != removed and not vis[i]:
return False
return True
if __name__ == "__main__":
V = 5
adj = [
[1, 2, 3],
[0, 2],
[1, 0, 4],
[0, 4],
[3, 2]
]
print("true" if isBiconnected(V, adj) else "false")
using System;
using System.Collections.Generic;
class GFG
{
static void DFS(int u, int removed, List<List<int>> adj, bool[] vis)
{
vis[u] = true;
// Recur for all the vertices adjacent to this vertex.
foreach (int v in adj[u])
{
// If an adjacent vertex is not removed and
// not visited, then recur for that adjacent.
if (v != removed && !vis[v])
{
DFS(v, removed, adj, vis);
}
}
}
// Returns true if the graph
// is biconnected, else false.
static bool IsBiconnected(int V, List<List<int>> adj)
{
// Remove each vertex and check
// whether the graph remains connected.
for (int removed = 0; removed < V; removed++)
{
bool[] vis = new bool[V];
int start = -1;
// Find a starting vertex other
// than the removed vertex.
for (int i = 0; i < V; i++)
{
if (i != removed)
{
start = i;
break;
}
}
DFS(start, removed, adj, vis);
// Check if all remaining vertices
// are reachable or not.
for (int i = 0; i < V; i++)
{
if (i != removed && !vis[i])
return false;
}
}
return true;
}
static void Main()
{
int V = 5;
List<List<int>> adj = new List<List<int>>
{
new List<int> {1, 2, 3},
new List<int> {0, 2},
new List<int> {1, 0, 4},
new List<int> {0, 4},
new List<int> {3, 2}
};
Console.WriteLine(IsBiconnected(V, adj));
}
}
function dfs(u, removed, adj, vis) {
vis[u] = true;
// Recur for all the vertices adjacent to this vertex.
for (const v of adj[u]) {
// If an adjacent vertex is not removed and
// not visited, then recur for that adjacent.
if (v !== removed && !vis[v]) {
dfs(v, removed, adj, vis);
}
}
}
// Returns true if the graph
// is biconnected, else false.
function isBiconnected(V, adj) {
// Remove each vertex and check
// whether the graph remains connected.
for (let removed = 0; removed < V; removed++) {
const vis = new Array(V).fill(false);
let start = -1;
// Find a starting vertex other
// than the removed vertex.
for (let i = 0; i < V; i++) {
if (i !== removed) {
start = i;
break;
}
}
dfs(start, removed, adj, vis);
// Check if all remaining vertices
// are reachable or not.
for (let i = 0; i < V; i++) {
if (i !== removed && !vis[i]) {
return false;
}
}
}
return true;
}
// Driver code
const V = 5;
const adj = [
[1, 2, 3],
[0, 2],
[1, 0, 4],
[0, 4],
[3, 2]
];
console.log(isBiconnected(V, adj));
Output
true
[Expected Approach] - Using Depth First Search (Tarjan's Algorithm) - O(V + E) Time and O(V) Space
The idea is to use Depth First Search (DFS) to verify both conditions required for a graph to be Biconnected. A graph is biconnected if it is connected and does not contain any articulation point (cut vertex). During DFS traversal, we maintain the discovery time and the lowest reachable discovery time for each vertex. For every DFS tree edge (u, v), if the subtree rooted at v cannot reach any ancestor of u, then u becomes an articulation point. This condition can be detected using the low[] and disc[] arrays. The root of the DFS tree is a special case and becomes an articulation point if it has more than one DFS child.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void dfs(int u, vector<vector<int>> &adj, vector<int> &disc,
vector<int> &low, int &timer, bool &isBiconnected,
int parent) {
// Store discovery time and
// lowest reachable time.
disc[u] = low[u] = ++timer;
int children = 0;
// Recur for all the vertices adjacent to this vertex
for (int v : adj[u]) {
// If an adjacent vertex is not visited,
// then recur for that adjacent
if (disc[v] == -1) {
children++;
dfs(v, adj, disc, low, timer, isBiconnected, u);
// Child may reach an earlier ancestor.
low[u] = min(low[u], low[v]);
// Root must have only one DFS child.
if (parent == -1 && children > 1)
isBiconnected = false;
// Child subtree cannot go above u.
else if (parent != -1 && low[v] >= disc[u])
isBiconnected = false;
}
// If an adjacent vertex is visited and is not
// parent of current vertex,
// then there exists a back edge.
else if (v != parent) {
low[u] = min(low[u], disc[v]);
}
}
}
// Returns true if the graph
// is biconnected, else false.
bool isBiconnected(int V, vector<vector<int>> &adj) {
vector<int> disc(V, -1), low(V, -1);
int timer = 0;
bool isBiconnectedGraph = true;
dfs(0, adj, disc, low, timer, isBiconnectedGraph, -1);
// Check if all vertices are visited.
for (int u = 0; u < V; u++) {
if (disc[u] == -1)
return false;
}
return isBiconnectedGraph;
}
int main() {
int V = 5;
vector<vector<int>> adj = {
{1, 2, 3},
{0, 2},
{1, 0, 4},
{0, 4},
{3, 2}
};
isBiconnected(V, adj) ? cout << "true" : cout << "false";
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
static int[] disc, low;
static int timer;
static boolean isBiconnectedGraph;
static void dfs(int u, ArrayList<ArrayList<Integer>> adj, int parent) {
// Store discovery time and
// lowest reachable time.
disc[u] = low[u] = ++timer;
int children = 0;
// Recur for all the vertices adjacent to this vertex
for (int v : adj.get(u)) {
// If an adjacent vertex is not visited,
// then recur for that adjacent
if (disc[v] == -1) {
children++;
dfs(v, adj, u);
// Child may reach an earlier ancestor.
low[u] = Math.min(low[u], low[v]);
// Root must have only one DFS child.
if (parent == -1 && children > 1)
isBiconnectedGraph = false;
// Child subtree cannot go above u.
else if (parent != -1 && low[v] >= disc[u])
isBiconnectedGraph = false;
}
// If an adjacent vertex is visited and is not
// parent of current vertex,
// then there exists a back edge.
else if (v != parent) {
low[u] = Math.min(low[u], disc[v]);
}
}
}
// Returns true if the graph
// is biconnected, else false.
static boolean isBiconnected(int V, ArrayList<ArrayList<Integer>> adj) {
disc = new int[V];
low = new int[V];
Arrays.fill(disc, -1);
Arrays.fill(low, -1);
timer = 0;
isBiconnectedGraph = true;
dfs(0, adj, -1);
// Check if all vertices are visited.
for (int u = 0; u < V; u++) {
if (disc[u] == -1)
return false;
}
return isBiconnectedGraph;
}
public static void main(String[] args) {
int V = 5;
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
adj.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
adj.add(new ArrayList<>(Arrays.asList(0, 2)));
adj.add(new ArrayList<>(Arrays.asList(1, 0, 4)));
adj.add(new ArrayList<>(Arrays.asList(0, 4)));
adj.add(new ArrayList<>(Arrays.asList(3, 2)));
System.out.println(isBiconnected(V, adj) ? "true" : "false");
}
}
def dfs(u, adj, disc, low, timer, parent):
# Store discovery time and
# lowest reachable time.
timer[0] += 1
disc[u] = low[u] = timer[0]
children = 0
# Recur for all the vertices adjacent to this vertex
for v in adj[u]:
# If an adjacent vertex is not visited,
# then recur for that adjacent
if disc[v] == -1:
children += 1
dfs(v, adj, disc, low, timer, u)
# Child may reach an earlier ancestor.
low[u] = min(low[u], low[v])
# Root must have only one DFS child.
if parent == -1 and children > 1:
timer[1] = False
# Child subtree cannot go above u.
elif parent != -1 and low[v] >= disc[u]:
timer[1] = False
# If an adjacent vertex is visited and is not
# parent of current vertex,
# then there exists a back edge.
elif v != parent:
low[u] = min(low[u], disc[v])
# Returns true if the graph
# is biconnected, else false.
def isBiconnected(V, adj):
disc = [-1] * V
low = [-1] * V
# timer[0] = time, timer[1] = isBiconnectedGraph
timer = [0, True]
dfs(0, adj, disc, low, timer, -1)
# Check if all vertices are visited.
for u in range(V):
if disc[u] == -1:
return False
return timer[1]
if __name__ == "__main__":
V = 5
adj = [
[1, 2, 3],
[0, 2],
[1, 0, 4],
[0, 4],
[3, 2]
]
print("true" if isBiconnected(V, adj) else "false")
using System;
using System.Collections.Generic;
class Solution {
static int[] disc, low;
static int timer;
static bool isBiconnectedGraph;
static void Dfs(int u, List<List<int>> adj, int parent) {
// Store discovery time and
// lowest reachable time.
disc[u] = low[u] = ++timer;
int children = 0;
// Recur for all the vertices adjacent to this vertex
foreach (int v in adj[u]) {
// If an adjacent vertex is not visited,
// then recur for that adjacent
if (disc[v] == -1) {
children++;
Dfs(v, adj, u);
// Child may reach an earlier ancestor.
low[u] = Math.Min(low[u], low[v]);
// Root must have only one DFS child.
if (parent == -1 && children > 1)
isBiconnectedGraph = false;
// Child subtree cannot go above u.
else if (parent != -1 && low[v] >= disc[u])
isBiconnectedGraph = false;
}
// If an adjacent vertex is visited and is not
// parent of current vertex,
// then there exists a back edge.
else if (v != parent) {
low[u] = Math.Min(low[u], disc[v]);
}
}
}
// Returns true if the graph
// is biconnected, else false.
static bool IsBiconnected(int V, List<List<int>> adj) {
disc = new int[V];
low = new int[V];
for (int i = 0; i < V; i++) disc[i] = low[i] = -1;
timer = 0;
isBiconnectedGraph = true;
Dfs(0, adj, -1);
// Check if all vertices are visited.
for (int u = 0; u < V; u++) {
if (disc[u] == -1)
return false;
}
return isBiconnectedGraph;
}
static void Main(string[] args) {
int V = 5;
List<List<int>> adj = new List<List<int>> {
new List<int> {1, 2, 3},
new List<int> {0, 2},
new List<int> {1, 0, 4},
new List<int> {0, 4},
new List<int> {3, 2}
};
Console.WriteLine(IsBiconnected(V, adj) ? "true" : "false");
}
}
function dfs(u, adj, disc, low, timer, parent) {
// Store discovery time and
// lowest reachable time.
disc[u] = low[u] = ++timer[0];
let children = 0;
// Recur for all the vertices adjacent to this vertex
for (let v of adj[u]) {
// If an adjacent vertex is not visited,
// then recur for that adjacent
if (disc[v] === -1) {
children++;
dfs(v, adj, disc, low, timer, u);
// Child may reach an earlier ancestor.
low[u] = Math.min(low[u], low[v]);
// Root must have only one DFS child.
if (parent === -1 && children > 1)
timer[1] = false;
// Child subtree cannot go above u.
else if (parent !== -1 && low[v] >= disc[u])
timer[1] = false;
}
// If an adjacent vertex is visited and is not
// parent of current vertex,
// then there exists a back edge.
else if (v !== parent) {
low[u] = Math.min(low[u], disc[v]);
}
}
}
// Returns true if the graph
// is biconnected, else false.
function isBiconnected(V, adj) {
let disc = new Array(V).fill(-1);
let low = new Array(V).fill(-1);
// timer[0] = time, timer[1] = isBiconnectedGraph
let timer = [0, true];
dfs(0, adj, disc, low, timer, -1);
// Check if all vertices are visited.
for (let u = 0; u < V; u++) {
if (disc[u] === -1)
return false;
}
return timer[1];
}
// Driver code
const V = 5;
const adj = [
[1, 2, 3],
[0, 2],
[1, 0, 4],
[0, 4],
[3, 2]
];
console.log(isBiconnected(V, adj) ? "true" : "false");
Output
true


