Python 如何使用DFS了解节点执行(调用前、调用中、调用后)

Python 如何使用DFS了解节点执行(调用前、调用中、调用后),python,depth-first-search,Python,Depth First Search,假设我实现了一个迭代DFS的简单版本,如下所示: import sys import traceback def dfs(graph, start): visited, stack = [], [start] while stack: node = stack.pop() if node not in visited: visited.append(node) childs = reversed

假设我实现了一个迭代DFS的简单版本,如下所示:

import sys
import traceback


def dfs(graph, start):
    visited, stack = [], [start]
    while stack:
        node = stack.pop()

        if node not in visited:
            visited.append(node)
            childs = reversed(graph.get(node, list()))
            stack.extend([item for item in childs if item not in visited])

    return visited

if __name__ == "__main__":
    graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

    for i, g in enumerate(graphs):
        try:
            print "{0}Graph {1}{2}".format('-' * 40, i, '-' * 33)

            for f in [dfs]:
                print f.__name__, '-->', f(g, 'A')
                print '-' * 80
        except Exception as e:
            print "Exception in user code: {0}".format(e)
            print '-' * 60
            traceback.print_exc(file=sys.stdout)
            print '-' * 60
上述代码段的输出如下所示:

----------------------------------------Graph 0---------------------------------
dfs --> ['A', 'B', 'D', 'C']
--------------------------------------------------------------------------------
现在,我正试图找出如何获得以下输出,而不是运行node的方法,只需打印即可:

A_start, B_start, D_start, D_end, B_end, A_middle, C_start, C_end, A_end
*_中间将仅在子节点执行之间执行。例如,如果一个节点没有任何子节点,或者只有一个子节点,那么它永远不会执行。这就是为什么我想要的输出只有A_中间,而在上面的例子中没有B_中间,C_中间,D_中间

我该怎么做

编辑:

试图找到问题的递归解决方案:

def dfs(graph, node):
    if node not in graph:
        return

    print '{0}_start'.format(node)

    for i, node in enumerate(graph[node]):
        if i > 0:
            print '{0}_middle'.format(node)

        dfs(graph, node)

    print '{0}_end'.format(node)

if __name__ == "__main__":
    graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

    for i, g in enumerate(graphs):
        try:
            print "{0}Graph {1}{2}".format('-' * 40, i, '-' * 33)

            for f in [dfs]:
                print f.__name__, '-->'
                f(g, 'A')
                print '-' * 80
        except Exception as e:
            print "Exception in user code: {0}".format(e)
            print '-' * 60
            traceback.print_exc(file=sys.stdout)
            print '-' * 60
将给我错误的输出:

----------------------------------------Graph 0---------------------------------
dfs -->
A_start
B_start
D_end
C_middle
C_end
--------------------------------------------------------------------------------

实际上,您的递归尝试非常接近。 我为我所做的小调整添加了评论

import sys, traceback

def dfs(graph, node):
    print '{0}_start'.format(node)  # need this right at the top
    if node not in graph:
        print '{0}_end'.format(node)  # need to record the end if we can't find
        return

    for i, nd in enumerate(graph[node]):  # need a different `node` variable here!!!
        if i > 0:
            print '{0}_middle'.format(node)

        dfs(graph, nd)

    print '{0}_end'.format(node)

if __name__ == "__main__":
    graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

    for i, g in enumerate(graphs):
        try:
            print "{0}Graph {1}{2}".format('-' * 40, i, '-' * 33)

            for f in [dfs]:
                print f.__name__, '-->'
                f(g, 'A')
                print '-' * 80
        except Exception as e:
            print "Exception in user code: {0}".format(e)
            print '-' * 60
            traceback.print_exc(file=sys.stdout)
            print '-' * 60

这将生成您正在寻找的输出。

实际上,您的递归尝试非常接近。 我为我所做的小调整添加了评论

import sys, traceback

def dfs(graph, node):
    print '{0}_start'.format(node)  # need this right at the top
    if node not in graph:
        print '{0}_end'.format(node)  # need to record the end if we can't find
        return

    for i, nd in enumerate(graph[node]):  # need a different `node` variable here!!!
        if i > 0:
            print '{0}_middle'.format(node)

        dfs(graph, nd)

    print '{0}_end'.format(node)

if __name__ == "__main__":
    graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

    for i, g in enumerate(graphs):
        try:
            print "{0}Graph {1}{2}".format('-' * 40, i, '-' * 33)

            for f in [dfs]:
                print f.__name__, '-->'
                f(g, 'A')
                print '-' * 80
        except Exception as e:
            print "Exception in user code: {0}".format(e)
            print '-' * 60
            traceback.print_exc(file=sys.stdout)
            print '-' * 60

这会产生您想要的输出。

我怀疑这正是您想要的

graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

def dfs(graph, start):
    print '{}_start'.format(start)
    try:
        for child in graph[start]:
            dfs(graph, child)
            print '{}_middle'.format(start)
    except KeyError:
        # We found a leaf node, it has no children.
        pass
    print '{}_end'.format(start)   

# Test it for one graph
dfs(graphs[0], 'A')

# Output:

# A_start
# B_start
# D_start
# D_end
# B_middle
# B_end
# A_middle
# C_start
# C_end
# A_middle
# A_end

我怀疑这正是你想要的

graphs = [
        {
            'A': ['B', 'C'],
            'B': ['D']
        }
    ]

def dfs(graph, start):
    print '{}_start'.format(start)
    try:
        for child in graph[start]:
            dfs(graph, child)
            print '{}_middle'.format(start)
    except KeyError:
        # We found a leaf node, it has no children.
        pass
    print '{}_end'.format(start)   

# Test it for one graph
dfs(graphs[0], 'A')

# Output:

# A_start
# B_start
# D_start
# D_end
# B_middle
# B_end
# A_middle
# C_start
# C_end
# A_middle
# A_end

正如其他答案所示,当前递归代码的主要问题是基本情况:

if node not in graph:
    return
当节点中没有子节点时,这会错误地跳过输出。去掉这些行,只需在for循环中使用EnumeratGraph.getstart[]而不是EnumeratGraph[start],它就可以正常工作

使迭代代码工作起来要复杂得多。一种尝试方法是将2元组推送到堆栈中。第一个值将是一个节点,如前所述,但第二个值将是该节点的前一个,因此我们可以为父节点打印中间消息,或者不打印任何值,指示我们需要打印该节点的结束标记

然而,跟踪访问了哪些节点变得有点复杂。我使用的不是单个节点列表,而是从节点到整数的字典映射。不存在的值表示尚未访问该节点。1表示节点已被访问,并且已打印其开始消息。2表示至少访问了节点的一个子节点,并且每个子节点都应该代表父节点打印一条中间消息。A 3表示已打印结束消息

def dfs(graph, start):
    visited = {}
    stack = [(start, "XXX_THIS_NODE_DOES_NOT_EXIST_XXX")]
    while stack:
        node, parent = stack.pop()
        if parent is None:
            if visited[node] < 3:
                print "{}_end".format(node)
            visited[node] = 3

        elif node not in visited:
            if visited.get(parent) == 2:
                print "{}_middle".format(parent)
            elif visited.get(parent) == 1:
                visited[parent] = 2

            print "{}_start".format(node)
            visited[node] = 1
            stack.append((node, None))
            for child in reversed(graph.get(node, [])):
                if child not in visited:
                    stack.append((child, node))

因为我正在使用字典进行访问,所以在末尾返回它可能不合适,所以我删除了return语句。我认为如果您真的想恢复它,可以使用collections.orderedict而不是普通dict,并返回其键。

正如其他答案所示,当前递归代码的主要问题是基本情况:

if node not in graph:
    return
当节点中没有子节点时,这会错误地跳过输出。去掉这些行,只需在for循环中使用EnumeratGraph.getstart[]而不是EnumeratGraph[start],它就可以正常工作

使迭代代码工作起来要复杂得多。一种尝试方法是将2元组推送到堆栈中。第一个值将是一个节点,如前所述,但第二个值将是该节点的前一个,因此我们可以为父节点打印中间消息,或者不打印任何值,指示我们需要打印该节点的结束标记

然而,跟踪访问了哪些节点变得有点复杂。我使用的不是单个节点列表,而是从节点到整数的字典映射。不存在的值表示尚未访问该节点。1表示节点已被访问,并且已打印其开始消息。2表示至少访问了节点的一个子节点,并且每个子节点都应该代表父节点打印一条中间消息。A 3表示已打印结束消息

def dfs(graph, start):
    visited = {}
    stack = [(start, "XXX_THIS_NODE_DOES_NOT_EXIST_XXX")]
    while stack:
        node, parent = stack.pop()
        if parent is None:
            if visited[node] < 3:
                print "{}_end".format(node)
            visited[node] = 3

        elif node not in visited:
            if visited.get(parent) == 2:
                print "{}_middle".format(parent)
            elif visited.get(parent) == 1:
                visited[parent] = 2

            print "{}_start".format(node)
            visited[node] = 1
            stack.append((node, None))
            for child in reversed(graph.get(node, [])):
                if child not in visited:
                    stack.append((child, node))

因为我正在使用字典进行访问,所以在末尾返回它可能不合适,所以我删除了return语句。我认为如果您真的想恢复它,可以使用collections.orderedict而不是普通dict,并返回其键。

事实上,您的迭代实现是在读取其子节点后立即完成的。递归实现将使您更容易获得所需的输出。否则,您将需要另一个数据结构来跟踪何时处理在读取节点时实际添加到堆栈中的节点的子节点。事实上,您的迭代实现是在读取其子节点后立即完成的。递归实现将使您更容易获得所需的输出。否则,您将需要另一个数据结构来跟踪何时处理在读取该节点时实际添加到堆栈中的节点的子节点。缩进中出现错误,请查看编辑。此外,如果这个答案符合您的要求,请记住将其标记为已接受!为了满足规定的要求,您需要
只需要在同一节点的不同子节点之间打印中间消息。@KennyOstrom,你是说这样做不行吗?我看到的唯一的边缘情况是某个东西有一个孩子。如果要在两个同级节点之间显式打印,则需要在对子节点进行迭代的for循环中附加一个条件。缩进中出现错误,请查看编辑。此外,如果这个答案符合您的要求,请记住将其标记为已接受!为了满足规定的要求,您只需要在同一节点的不同子节点之间打印中间消息。@KennyOstrom,您是说这样做不行吗?我看到的唯一的边缘情况是某个东西有一个孩子。如果您希望它在两个同级节点之间显式打印,那么在遍历子节点的for循环中需要有一个附加条件。谢谢,这很完美。它解决了我在设计这个迭代算法时遇到的主要问题。我已经在我的问题中添加了一些与我的验证答案决策相关的评论。另一个与此相关的是Hanks,它很完美。它解决了我在设计这个迭代算法时遇到的主要问题。我在我的问题中添加了一些与我的验证答案决策相关的评论,另一个与此相关