简体   繁体   English

在有向图上对 DFS 中的边进行分类

[英]Classifying edges in DFS on a directed graph

Based on a DFS traversal, I want to classify edges (u, v) in a directed graph as:基于 DFS 遍历,我想将有向图中的边 (u, v) 分类为:

  • Tree edge: when v is visited for the first time as we traverse the edge树边:当我们遍历边时第一次访问 v 时
  • Back edge: when v is an ancestor of u in the traversal tree后边:当 v 是遍历树中 u 的祖先时
  • Forward edge: when v is a descendant of u in the traversal tree前向边:当 v 是遍历树中 u 的后代时
  • Cross edge: when v is neither an ancestor or descendant of u in the traversal tree交叉边:当 v 在遍历树中既不是 u 的祖先也不是它的后代

I was following a GeeksForGeeks tutorial to write this code:我正在按照GeeksForGeeks 教程编写这段代码:

class Graph:
    def __init__(self, v):
        self.time = 0
        self.traversal_array = []
        self.v = v
        self.graph_list = [[] for _ in range(v)]
 
    def dfs(self):
        self.visited = [False]*self.v
        self.start_time = [0]*self.v
        self.end_time = [0]*self.v
        self.ff = 0
        self.fc = 0
 
        for node in range(self.v):
            if not self.visited[node]:
                self.traverse_dfs(node)

    def traverse_dfs(self, node):
        # mark the node visited
        self.visited[node] = True
        # add the node to traversal
        self.traversal_array.append(node)
        # get the starting time
        self.start_time[node] = self.time
        # increment the time by 1
        self.time += 1
        # traverse through the neighbours
        for neighbour in self.graph_list[node]:
            # if a node is not visited
            if not self.visited[neighbour]:
                # marks the edge as tree edge
                print('Tree Edge:', str(node)+'-->'+str(neighbour))
                # dfs from that node
                self.traverse_dfs(neighbour)
            else:
                # when the parent node is traversed after the neighbour node
                if self.start_time[node] > self.start_time[neighbour] and self.end_time[node] < self.end_time[neighbour]:
                    print('Back Edge:', str(node)+'-->'+str(neighbour))
                # when the neighbour node is a descendant but not a part of tree
                elif self.start_time[node] < self.start_time[neighbour] and self.end_time[node] > self.end_time[neighbour]:
                    print('Forward Edge:', str(node)+'-->'+str(neighbour))
                # when parent and neighbour node do not have any ancestor and a descendant relationship between them
                elif self.start_time[node] > self.start_time[neighbour] and self.end_time[node] > self.end_time[neighbour]:
                    print('Cross Edge:', str(node)+'-->'+str(neighbour))
            self.end_time[node] = self.time
            self.time += 1          

But it does not output the desired results for the following graph:但它并没有 output 下图的预期结果:

在此处输入图像描述

which is represented with:表示为:

self.v = 3
self.graph_list = [[1, 2], [], [1]]

The above code is not identifying the edge (2, 1) as a cross edge, but as a back edge.上面的代码没有将边 (2, 1) 识别为交叉边,而是识别为后边。

I have no clue what to adapt in my code in order to detect cross edges correctly.我不知道要在我的代码中调整什么才能正确检测交叉边缘。

In a discussion someone gave this information, but I couldn't make work:在讨论中有人提供了这些信息,但我无法工作:

The checking condition is wrong when the node has not been completely visited when the edge is classified.当对边进行分类时节点还没有被完全访问时,检查条件是错误的。 This is because in the initial state the start & end times are set to 0.这是因为在初始 state 中,开始和结束时间设置为 0。

if the graph looks like this:如果图表看起来像这样:

  • 0 --> 1 0 --> 1
  • 1 --> 2 1 --> 2
  • 2 --> 3 2 --> 3
  • 3 --> 1 3 --> 1

When checking the 3 --> 1 edge: the answer should be a back edge.检查 3 --> 1 边缘时:答案应该是后边缘。

But now the start/end [3] = 4/0 ;但是现在start/end [3] = 4/0 ; start/end [1] = 1/0

and the condition end[3] < end[1] is false because of the intialization problem.由于初始化问题,条件end[3] < end[1]为假。

I see two solutions,我看到两个解决方案,

  1. traverse the graph first and determine the correct start/end [i] values, but it needs more time complexity, or首先遍历图并确定正确的start/end [i]值,但它需要更多的时间复杂度,或者
  2. use black/gray/white and discover the order to classify the edges使用黑色/灰色/白色并发现对边缘进行分类的顺序

Here are some issues:这里有一些问题:

  • By initialising start_time and end_time to 0 for each node, you cannot make the difference with a real time of 0, which is assigned to the very first node's start time.通过将每个节点的start_timeend_time初始化为 0,您无法区分实际时间 0,它分配给第一个节点的开始时间。 You should initialise these lists with a value that indicates there was no start/end at all .您应该使用指示根本没有开始/结束值来初始化这些列表。 You could use the value -1 for this purpose.为此,您可以使用值 -1。

  • The following statements should not be inside the loop:以下语句不应在循环内:

     self.end_time[node] = self.time self.time += 1

    They should be executed after the loop has completed.它们应该在循环完成执行。 Only at that point you can "end" the visit of the current node.只有到那时你才能“结束”当前节点的访问。 So the indentation of these two statements should be less.所以这两个语句的缩进应该少一些。

  • There are several places where the value of self.end_time[node] is compared in a condition, but that time has not been set yet (apart from its default value), so this condition makes little sense. self.end_time[node]的值在条件中有几个地方被比较,但那个时间还没有设置(除了它的默认值),所以这个条件没有什么意义。

  • The last elif should really be an else because there are no other possibilities to cover.最后一个elif应该是else因为没有其他的可能性可以覆盖。 If ever the execution gets there, it means no other possibility remains, so no condition should be checked.如果执行到达那里,则意味着没有其他可能性,因此不应检查任何条件。

  • The condition self.start_time[node] > self.start_time[neighbour] is not strong enough for identifying a back edge, and as already said, the second part of that if condition makes no sense, since self.end_time[node] has not been given a non-default value yet.条件self.start_time[node] > self.start_time[neighbour]不足以识别后缘,并且如前所述, if条件的第二部分没有意义,因为self.end_time[node]没有尚未被赋予非默认值。 And so this if block is entered also when it is not a back edge.因此,当它不是后边缘时,也会输入此if块。 What you really want to test here, is that the visit of neighbor has not been closed yet.这里真正要测试的是, neighbor的访问还没有关闭。 In other words, you should check that self.start_time[neighbor] is still at its default value (and I propose to use -1 for that).换句话说,您应该检查self.start_time[neighbor]是否仍处于默认值(我建议为此使用 -1)。

Not a problem, but there are also these remarks to make:没问题,但也有以下几点需要说明:

  • when you keep track of start_time and end_time , there is no need to have visited .当您跟踪start_timeend_time时,就没有必要visited Whether a node is visited follows from the value of start_time : if it still has its default value (-1), then the node has not yet been visited.节点是否被访问取决于start_time的值:如果它仍然具有默认值(-1),则该节点尚未被访问。

  • Don't use code comments to state the obvious.不要对明显的 state 使用代码注释。 For instance the comment "increment the time by 1" really isn't explaining anything that cannot be seen directly from the code.例如,注释“将时间增加 1”实际上并没有解释无法从代码中直接看到的任何内容。

  • Attribute v could use a better name.属性v可以使用更好的名称。 Although V is often used to denote the set of nodes of a graph, it is not intuitive to see v as the number of nodes in the graph.尽管 V 通常用于表示图的节点,但将v视为图中的节点数并不直观。 I would suggest using num_nodes instead.我建议改用num_nodes It makes the code more readable.它使代码更具可读性。

Here is a correction of your code:这是您的代码的更正:

class Graph:
    def __init__(self, num_nodes):
        self.time = 0
        self.traversal_array = []
        self.num_nodes = num_nodes  # Use more descriptive name
        self.graph_list = [[] for _ in range(num_nodes)]
 
    def dfs(self):
        self.start_time = [-1]*self.num_nodes
        self.end_time = [-1]*self.num_nodes
        for node in range(self.num_nodes):
            if self.start_time[node] == -1:  # No need for self.visited
                self.traverse_dfs(node)

    def traverse_dfs(self, node):
        self.traversal_array.append(node)
        self.start_time[node] = self.time
        self.time += 1
        for neighbour in self.graph_list[node]:
            # when the neighbor was not yet visited
            if self.start_time[neighbour] == -1:
                print(f"Tree Edge: {node}-->{neighbour}")
                self.traverse_dfs(neighbour)
            # otherwise when the neighbour's visit is still ongoing:
            elif self.end_time[neighbour] == -1:
                print(f"Back Edge: {node}-->{neighbour}")
            # otherwise when the neighbour's visit started before the current node's visit:
            elif self.start_time[node] < self.start_time[neighbour]:
                print(f"Forward Edge: {node}-->{neighbour}")
            else: # No condition here: there are no other options
                print(f"Cross Edge: {node}-->{neighbour}")
        # Indentation corrected:
        self.end_time[node] = self.time
        self.time += 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM