Given a Graph of V vertices and E edges and another edge(c - d), the task is to find if the given edge is a Bridge. i.e., removing the edge disconnects the graph.
 
Example 1:
Input:
c = 1, d = 2
Output:
1
Explanation:
From the graph, we can clearly see that
blocking the edge 1-2 will result in 
disconnection of the graph. So, it is 
a Bridge and thus the Output 1.
Example 2:
Input:
c = 0, d = 2
Output:
0
Explanation:
blocking the edge between nodes 0 and 2
won't affect the connectivity of the graph.
So, it's not a Bridge Edge. All the Bridge
Edges in the graph are marked with a blue
line in the above image.
 
Your Task:
You don't need to read input or print anything. Your task is to complete the function isBridge()  which takes number of vertices V, the number of edges E, an adjacency lsit adj and two integers c and d denoting the edge as input parameters and returns 1 if the given edge c-d is a Bridge. Else, it returns 0.
 
Expected Time Complexity: O(V + E).
Expected Auxiliary Space: O(V).
 
Constraints:
1  ≤  V,E  ≤  10^{5}
0  ≤  c, d  ≤  V-1

Difficulty: MEDIUM
Tags: Graph algorithms

Data structures
Skill types: Data structures
Expected Time Complexity: O(V + E).
Expected Auxiliary Space: O(V).

Starter code:
#User function Template for python3

class Solution:
   
    #Function to find if the given edge is a bridge in graph.
    def isBridge(self, V, adj, c, d):
        # code here

Solutions:
class Solution:

	def dfs(self, sv, visited, tins, low, parent, output, time):
		visited[sv] = True
		tins[sv] = time
		low[sv] = time
		time += 1
		for neighbour in adj[sv]:
			if visited[neighbour] == False:
				parent[neighbour] = sv
				self.dfs(neighbour, visited, tins, low, parent, output, time)
				low[sv] = min(low[sv], low[neighbour])
				if low[neighbour] > tins[sv]:
					output.append([sv, neighbour])
			elif neighbour != parent[sv]:
				low[sv] = min(low[sv], tins[neighbour])

	def isBridge(self, V, adj, c, d):
		visited = [False for i in range(V)]
		tins = [0 for i in range(V)]
		low = [0 for i in range(V)]
		parent = [-1 for i in range(V)]
		output = []
		time = 0
		for i in range(V):
			if visited[i] == False:
				self.dfs(i, visited, tins, low, parent, output, time)
		for edge in output:
			if edge == [d, c] or edge == [c, d]:
				return 1
		return 0


class Solution:

	def isBridge(self, V, adj, c, d):
		if c in adj[d]:
			adj[d].remove(c)
		if d in adj[c]:
			adj[c].remove(d)
		else:
			return 0

		def dfs(node):
			visit.add(node)
			for ngh in adj[node]:
				if ngh not in visit:
					dfs(ngh)
		visit = set()
		dfs(c)
		if d in visit:
			return 0
		return 1


class Solution:

	def rf(self, sv, visited):
		visited[sv] = True
		for neighbour in adj[sv]:
			if visited[neighbour] == False:
				self.rf(neighbour, visited)
		return

	def isBridge(self, V, adj, c, d):
		if c in adj[d]:
			adj[d].remove(c)
		if d in adj[c]:
			adj[c].remove(d)
		else:
			return 0
		visited = [False for i in range(V)]
		self.rf(c, visited)
		if visited[d] == True:
			return 0
		return 1


class Solution:

	def isBridge(self, V, adj, c, d):

		def dfs(src):
			vis[src] = 0
			for i in adj[src]:
				if vis[i]:
					dfs(i)
		if c in adj[d]:
			adj[d].remove(c)
		if d in adj[c]:
			adj[c].remove(d)
		else:
			return 0
		vis = [1] * V
		dfs(c)
		return vis[d]


class Solution:

	def isBridge(self, V, adj, c, d):
		if c == d:
			return 0
		adj[c].remove(d)
		adj[d].remove(c)
		visited = set()
		stack = [c]
		while stack:
			node = stack.pop()
			if node in visited:
				continue
			visited.add(node)
			if node == d:
				return 0
			stack += adj[node]
		return 1


from math import inf

class Solution:

	def isBridge(self, V, adj, c, d):
		tin = [inf] * V
		low = [inf] * V
		visited = [0] * V
		timer = 1
		flag = 0
		mark = set()

		def dfs(node, par):
			nonlocal timer, flag
			visited[node] = 1
			low[node] = tin[node] = timer
			timer += 1
			for each in adj[node]:
				if each == par:
					continue
				if not visited[each]:
					dfs(each, node)
					low[node] = min(low[node], low[each])
					if low[each] > tin[node]:
						if sorted([each, node]) == sorted([c, d]):
							flag = 1
				else:
					low[node] = min(low[each], low[node])
			return 0
		for i in range(V):
			if not visited[i]:
				dfs(i, -1)
				if flag:
					return 1
		dfs(0, -1)
		return flag


class Solution:

	def isBridge(self, V, adj, c, d):
		if c == d:
			return 0
		visited = set()
		adj[c].remove(d)
		adj[d].remove(c)

		def dfs(sv):
			visited.add(sv)
			for des in adj[sv]:
				if des not in visited:
					dfs(des)
			return
		for i in range(V):
			if i not in visited:
				dfs(i)
				if c in visited and d not in visited or (d in visited and c not in visited):
					return 1
				if c in visited and d in visited:
					return 0
		return 0


class Solution:

	def isBridge(self, V, adj, c, d):

		def dfs(sv):
			visited[sv] = 1
			for u in adj[sv]:
				if visited[u] == 0:
					dfs(u)
		visited = [0 for i in range(V)]
		count = 0
		for i in range(V):
			if visited[i] == 0:
				count += 1
				dfs(i)
		if d in adj[c]:
			adj[c].remove(d)
		if c in adj[d]:
			adj[d].remove(c)
		visited = [0 for i in range(V)]
		t = 0
		for i in range(V):
			if visited[i] == 0:
				t += 1
				dfs(i)
		if count != t:
			return 1
		return 0


class Solution:

	def isBridge(self, V, adj, c, d):
		visited_before = set()
		self.dfs(c, -1, adj, visited_before)
		adj[c].remove(d)
		if c != d:
			adj[d].remove(c)
		visited = set()
		self.dfs(c, -1, adj, visited)
		if len(visited) < len(visited_before):
			return 1
		elif len(visited) == len(visited_before):
			return 0

	def dfs(self, node, parent, adj, visited):
		visited.add(node)
		for child in adj[node]:
			if child != parent:
				if child not in visited:
					self.dfs(child, node, adj, visited)


class Solution:

	def isBridge(self, V, adj, c, d):
		if d in adj[c]:
			adj[c].remove(d)
		else:
			return 0
		if c in adj[d]:
			adj[d].remove(c)
		else:
			return 0
		vis = [False] * V

		def chg_boo(adj, vis, i):
			vis[i] = True
			for j in adj[i]:
				if not vis[j]:
					chg_boo(adj, vis, j)
		chg_boo(adj, vis, c)
		if not vis[d]:
			return 1
		return 0


class Solution:

	def __init__(self):
		self.timer = 0
		self.res = []

	def isBridge(self, V, adj, c, d):
		low = [float('inf')] * V
		disc = [float('inf')] * V
		vis = [False] * V
		for i in range(V):
			if vis[i] == False:
				self.dfs(adj, low, disc, i, -1, vis)
		for (x, y) in self.res:
			if x == c and y == d:
				return 1
		return 0

	def dfs(self, adj, low, disc, u, par, vis):
		(low[u], disc[u]) = (self.timer, self.timer)
		self.timer += 1
		vis[u] = True
		for v in adj[u]:
			if vis[v] == False:
				self.dfs(adj, low, disc, v, u, vis)
				low[u] = min(low[u], low[v])
				if low[v] > disc[u]:
					self.res.append([u, v])
					self.res.append([v, u])
			if vis[v] == True and v != par:
				low[u] = min(disc[v], low[u])
			if v == par:
				continue


class Solution:

	def __init__(self):
		self.timer = 0

	def isBridge(self, V, adj, c, d):
		visited = [False for _ in range(V)]
		bridges = []
		parent = [-1 for _ in range(V)]
		discovery = [None for _ in range(V)]
		lowestNodeExceptPath = [None for _ in range(V)]
		for i in range(V):
			self.dfs(adj, i, visited, discovery, lowestNodeExceptPath, parent, bridges)
		return 1 if (c, d) in bridges or (d, c) in bridges else 0

	def dfs(self, adj, vertex, visited, discovery, lowestNodeExceptPath, parent, bridges):
		if visited[vertex]:
			return
		visited[vertex] = True
		discovery[vertex] = self.timer
		lowestNodeExceptPath[vertex] = self.timer
		self.timer += 1
		for neighborVertex in adj[vertex]:
			if neighborVertex == parent[vertex]:
				continue
			elif visited[neighborVertex]:
				lowestNodeExceptPath[vertex] = min(lowestNodeExceptPath[vertex], discovery[neighborVertex])
			else:
				parent[neighborVertex] = vertex
				self.dfs(adj, neighborVertex, visited, discovery, lowestNodeExceptPath, parent, bridges)
				lowestNodeExceptPath[vertex] = min(lowestNodeExceptPath[vertex], lowestNodeExceptPath[neighborVertex])
				if lowestNodeExceptPath[neighborVertex] > discovery[vertex]:
					bridges.append((vertex, neighborVertex))


from collections import defaultdict

class Solution:

	def isBridge(self, n, connections, c, d):
		result = []
		visited = [False] * n
		tIn = [0] * n
		lowIn = [0] * n
		self.timer = 1

		def dfs(node, parent):
			visited[node] = True
			tIn[node] = lowIn[node] = self.timer
			self.timer += 1
			for adj in connections[node]:
				if adj == parent:
					continue
				if visited[adj] == False:
					dfs(adj, node)
					lowIn[node] = min(lowIn[node], lowIn[adj])
					if lowIn[adj] > tIn[node]:
						result.append([node, adj])
				else:
					lowIn[node] = min(lowIn[adj], lowIn[node])
		dfs(c, -1)
		if [c, d] in result or [d, c] in result:
			return 1
		return 0


class Solution:

	def __init__(self):
		self.bridges = []
		self.timer = 0

	def isBridge(self, V, adj, c, d):
		self.analyzeGraph(V, adj)
		if (c, d) in self.bridges or (d, c) in self.bridges:
			return 1
		else:
			return 0

	def analyzeGraph(self, V, adj):
		visited = {}
		graph = {}
		dis = {}
		low = {}
		for i in range(V):
			visited[i] = False
		for i in range(len(adj)):
			graph[i] = adj[i]
		for i in range(V):
			self.helper(V, graph, visited, dis, low, i, -1)

	def helper(self, V, graph, visited, dis, low, index, parent):
		visited[index] = True
		dis[index] = self.timer
		low[index] = self.timer
		self.timer += 1
		for edge in graph[index]:
			if edge == parent:
				continue
			if not visited[edge]:
				self.helper(V, graph, visited, dis, low, edge, index)
				low[index] = min(low[index], low[edge])
				if low[edge] > dis[index]:
					self.bridges.append((index, edge))
			else:
				low[index] = min(low[index], low[edge])


def dfs(node, adj, visited, c, d):
	stack = [c]
	visited[c] = True
	while stack:
		node = stack.pop()
		for u in adj[node]:
			if c == node and u == d or (c == d and u == node):
				continue
			if visited[u] == False:
				stack.append(u)
				visited[u] = True

class Solution:

	def isBridge(self, V, adj, c, d):
		visited = [False for _ in range(V)]
		dfs(c, adj, visited, c, d)
		if visited[d] == False:
			return 1
		return 0


def dfs(node, graph, visited):
	visited[node] = True
	for k in graph[node]:
		if visited[k] == False:
			dfs(k, graph, visited)

class Solution:

	def isBridge(self, V, adj, c, d):
		visited = [False for _ in range(V)]
		graph = [[] for _ in range(V)]
		for i in range(V):
			for u in adj[i]:
				if c == i and u == d or (c == d and u == i):
					continue
				else:
					graph[i].append(u)
		dfs(c, graph, visited)
		if visited[d] == False:
			return 1
		return 0


class Solution:

	def isBridge(self, V, adj, c, d):

		def number_of_connected_comp(V, adj):
			count = 0
			visited = [False] * V

			def dfs(node):
				visited[node] = True
				for ngbr in adj[node]:
					if not visited[ngbr]:
						dfs(ngbr)
			for i in range(V):
				if not visited[i]:
					dfs(i)
					count += 1
			return count
		count_1 = number_of_connected_comp(V, adj)
		if d in adj[c]:
			adj[c].remove(d)
		if c in adj[d]:
			adj[d].remove(c)
		count_2 = number_of_connected_comp(V, adj)
		if count_1 < count_2:
			return 1
		return 0


class Solution:

	def dfs(self, node, parent, visited, disc, low, timer, adj, edge):
		visited[node] = 1
		disc[node] = timer[0]
		low[node] = timer[0]
		timer[0] += 1
		for nbr in adj[node]:
			if nbr == parent:
				continue
			if visited[nbr] == 0:
				ans = self.dfs(nbr, node, visited, disc, low, timer, adj, edge)
				if ans:
					return True
				low[node] = min(low[node], low[nbr])
				if low[nbr] > disc[node] and nbr in edge and (node in edge):
					return True
			else:
				low[node] = min(low[node], disc[nbr])
		return False

	def isBridge(self, V, adj, c, d):
		visited = [0] * V
		disc = [-1] * V
		low = [-1] * V
		parent = -1
		timer = [0]
		for i in range(V):
			if visited[i] == 0:
				ans = self.dfs(i, parent, visited, disc, low, timer, adj, (c, d))
				if ans:
					return 1
		return 0


class DisjointSet:

	def __init__(self, n):
		self.root = [i for i in range(n)]
		self.rank = [0] * n

	def find(self, x):
		if self.root[x] == x:
			return x
		self.root[x] = self.find(self.root[x])
		return self.root[x]

	def union(self, x, y):
		xroot = self.find(x)
		yroot = self.find(y)
		if xroot != yroot:
			if self.rank[xroot] > self.rank[yroot]:
				self.root[yroot] = xroot
			elif self.rank[yroot] > self.rank[xroot]:
				self.root[xroot] = yroot
			else:
				self.root[yroot] = xroot
				self.rank[xroot] += 1

class Solution:

	def isBridge(self, V, adj, c, d):
		ds = DisjointSet(V)
		for i in range(V):
			for j in adj[i]:
				if i == c and j == d or (i == d and j == c):
					continue
				ds.union(i, j)
		if ds.find(c) == ds.find(d):
			return 0
		return 1