I am going through Tarjan's algorithm for finding articulation point in a graph using DFS.
https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/
Some notations:
low[] : It is an array of N elements which stores the discovery time of every vertex. It is initialized by 0.
disc[]: It is an array of N elements which stores, for every vertex v, the discovery time of the earliest discovered vertex to which v or any of the vertices in the subtree rooted at v is having a back edge. It is initialized by INFINITY.
Now the algorithm:
from collections import defaultdict
#This class represents an undirected graph
#using adjacency list representation
class Graph:
def __init__(self,vertices):
self.V= vertices #No. of vertices
self.graph = defaultdict(list) # default dictionary to store graph
self.Time = 0
# function to add an edge to graph
def addEdge(self,u,v):
self.graph[u].append(v)
self.graph[v].append(u)
'''A recursive function that find articulation points
using DFS traversal
u --> The vertex to be visited next
visited[] --> keeps tract of visited vertices
disc[] --> Stores discovery times of visited vertices
parent[] --> Stores parent vertices in DFS tree
ap[] --> Store articulation points'''
def APUtil(self,u, visited, ap, parent, low, disc):
#Count of children in current node
children =0
# Mark the current node as visited and print it
visited[u]= True
# Initialize discovery time and low value
disc[u] = self.Time
low[u] = self.Time
self.Time += 1
#Recur for all the vertices adjacent to this vertex
for v in self.graph[u]:
# If v is not visited yet, then make it a child of u
# in DFS tree and recur for it
if visited[v] == False :
parent[v] = u
children += 1
self.APUtil(v, visited, ap, parent, low, disc)
# Check if the subtree rooted with v has a connection to
# one of the ancestors of u
low[u] = min(low[u], low[v])
# u is an articulation point in following cases
# (1) u is root of DFS tree and has two or more chilren.
if parent[u] == -1 and children > 1:
ap[u] = True
#(2) If u is not root and low value of one of its child is more
# than discovery value of u.
if parent[u] != -1 and low[v] >= disc[u]:
ap[u] = True
# Update low value of u for parent function calls
elif v != parent[u]:
low[u] = min(low[u], disc[v])
#The function to do DFS traversal. It uses recursive APUtil()
def AP(self):
# Mark all the vertices as not visited
# and Initialize parent and visited,
# and ap(articulation point) arrays
visited = [False] * (self.V)
disc = [float("Inf")] * (self.V)
low = [float("Inf")] * (self.V)
parent = [-1] * (self.V)
ap = [False] * (self.V) #To store articulation points
# Call the recursive helper function
# to find articulation points
# in DFS tree rooted with vertex 'i'
for i in range(self.V):
if visited[i] == False:
self.APUtil(i, visited, ap, parent, low, disc)
for index, value in enumerate (ap):
if value == True: print index,
# Create a graph given in the above diagram
g1 = Graph(5)
g1.addEdge(1, 0)
g1.addEdge(0, 2)
g1.addEdge(2, 1)
g1.addEdge(0, 3)
g1.addEdge(3, 4)
print "\nArticulation points in first graph "
g1.AP()
g2 = Graph(4)
g2.addEdge(0, 1)
g2.addEdge(1, 2)
g2.addEdge(2, 3)
print "\nArticulation points in second graph "
g2.AP()
g3 = Graph (7)
g3.addEdge(0, 1)
g3.addEdge(1, 2)
g3.addEdge(2, 0)
g3.addEdge(1, 3)
g3.addEdge(1, 4)
g3.addEdge(1, 6)
g3.addEdge(3, 5)
g3.addEdge(4, 5)
print "\nArticulation points in third graph "
g3.AP()
#This code is contributed by Neelam Yadav
In this algorithm, the line of interest is:
low[u] = min(low[u], low[v])
This line is easy to understand. The earliest discovered vertex connected to u via a backedge = The earliest discovered vertex connected to any of its child nodes(v) via a backedge
OK. Now the base condition?
elif v != parent[u]:
low[u] = min(low[u], disc[v])
This also is easy to understand: If the vertex v connected to u has already been visited (check the if condition corresponding to this elif) "somehow" and v is not u's parent, then update low[u] to include disc[v].
NOW MY QUESTION:
Just because v has already been visited, you know that the edge (u,v) is not a tree edge. But how can you say for sure that it is a back edge? According to Tarjan's algorithm:
low[u] = min(disc[u], disc[w]) where w is an ancestor of u and there is a back edge from some descendant of u to w.
If it is not a tree edge, it can be forward edge, back edge or a cross edge. To identify the back edge from among these 3 types of edges, we need the start time and end times for each vertex. We don't do any of those checks here. Then how does we suppose that the update we are making is indeed using a back edge?