5

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?

c0der
  • 18,467
  • 6
  • 33
  • 65
user257330
  • 63
  • 13

1 Answers1

2

You are correct that in directed graphs there are four kinds of edges: tree, back, forward and cross edges. Wikipedia has brief definitions and an explanatory diagram. However, in undirected graphs there are only tree and back edges. Why is that?

  • Forward edges (1-8 in the link above): In a directed graph, let us consider a forward edge (u, v). The DFS algorithm first visits u, then reaches v via a different path (1-5-6-8 in the link above), returns from that path, then considers the edge (u, v) and says "Oh, I have already visited v; from the discovery/finish times I can see that v is a descendant of u, which means that (u, v) is a forward edge." But in an undirected graph, the edge (u, v) will first be considered in reverse while visiting the node v, so it becomes a back edge (v, u).
  • Cross edges (6-3 in the link above): By a similar process, a cross edge (u, v) in a directed graph will be discovered in reverse in the undirected graph and becomes a tree edge (v, u). In the example, from node 3 we visit node 4, then node 6, which becomes a child of 3.
Cătălin Frâncu
  • 1,179
  • 8
  • 15