我正在学习使用DFS查找图中的关节点,这被称为Tarjan算法。
一些注释: https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/
现在是算法:
这也很容易理解:如果与 u 相连的顶点 v 已经被访问过(检查与此 elif 对应的 if 条件),并且 v 不是 u 的父节点,则将 disc[v] 包括在 low[u] 中以更新它。
现在我的问题是:只因为 v 已经被访问过,你就知道边 (u,v) 不是一条树边。但你怎么能确定它是一条反向边呢? 根据 Tarjan 算法: low[u] = min(disc[u], disc[w]),其中 w 是 u 的祖先,且存在从 u 的某个后代到 w 的反向边。
如果它不是一条树边,它可以是前向边、反向边或横叉边。要从这 3 种类型的边中识别出反向边,我们需要每个顶点的起始时间和结束时间。我们没有进行任何这些检查。那么我们如何确信我们正在进行的更新确实使用了一条反向边呢?
一些注释: https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/
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.
现在是算法:
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
在这个算法中,我们感兴趣的是以下代码行:
low[u] = min(low[u], low[v])
这行文字很容易理解。 通过后向边与u相连的最早发现的顶点 = 通过后向边与任何一个子节点(v)相连的最早发现的顶点。
好的,现在是基本条件?
elif v != parent[u]:
low[u] = min(low[u], disc[v])
这也很容易理解:如果与 u 相连的顶点 v 已经被访问过(检查与此 elif 对应的 if 条件),并且 v 不是 u 的父节点,则将 disc[v] 包括在 low[u] 中以更新它。
现在我的问题是:只因为 v 已经被访问过,你就知道边 (u,v) 不是一条树边。但你怎么能确定它是一条反向边呢? 根据 Tarjan 算法: low[u] = min(disc[u], disc[w]),其中 w 是 u 的祖先,且存在从 u 的某个后代到 w 的反向边。
如果它不是一条树边,它可以是前向边、反向边或横叉边。要从这 3 种类型的边中识别出反向边,我们需要每个顶点的起始时间和结束时间。我们没有进行任何这些检查。那么我们如何确信我们正在进行的更新确实使用了一条反向边呢?