简体   繁体   English

在图中实现(修改的)DFS

[英]Implementing a (modified) DFS in a Graph

I have implemented a simple graph data structure in Python with the following structure below. 我已经在Python中实现了一个简单的图形数据结构,其结构如下。 The code is here just to clarify what the functions/variables mean, but they are pretty self-explanatory so you can skip reading it. 这里的代码只是为了阐明功能/变量的含义,但是它们很容易解释,因此您可以跳过阅读。

class Node: 

    def __init__(self, label):   
        self.out_edges = []
        self.label = label
        self.is_goal = False
        self.is_visited = False


    def add_edge(self, node, weight):              
        self.out_edges.append(Edge(node, weight))

    def visit(self):                              
        self.is_visited = True


class Edge:

    def __init__(self, node, weight):              
        self.node = node
        self.weight = weight


    def to(self):                                  
        return self.node


class Graph:    

    def __init__(self): 
        self.nodes = []


    def add_node(self, label):                    
        self.nodes.append(Node(label))


    def visit_nodes(self):                                                 
        for node in self.nodes:
            node.is_visited = True

Now I am trying to implement a depth-first search which starts from a given node v , and returns a path (in list form) to a goal node. 现在,我尝试实现从给定节点v开始的深度优先搜索,并返回到目标节点的路径(以列表形式)。 By goal node, I mean a node with the attribute is_goal set to true . 对于目标节点,我的意思是将属性is_goal设置为true的节点。 If a path exists, and a goal node is found, the string ':-)' is added to the list. 如果存在路径,并且找到目标节点,则将字符串':-)'添加到列表中。 Otherwise, the function just performs a DFS and goes as far as it can go. 否则,该功能将仅执行DFS并尽其所能。 (I do this here just to easily check whether a path exists or not). (我在这里这样做只是为了轻松检查路径是否存在)。

This is my implementation: 这是我的实现:

def dfs(G, v):
    path = []                                   # path is empty so far

    v.visit()                                   # mark the node as visited
    path.append(v.label)                        # add to path

    if v.is_goal:                               # if v is a goal node
        path.append(':-)')                      # indicate a path is reached
        G.visit_nodes()                         # set all remaining nodes to visited

    else:
        for edge in v.out_edges:                # for each out_edge of the starting node
            if not edge.to().is_visited:        # if the node pointed to is not visited
                path += dfs(G, edge.to())       # return the path + dfs starting from that node

    return path

Now the problem is, I have to set all the nodes to visited (line 9, visit_nodes() ) for the algorithm to end once a goal node is reached. 现在的问题是,我必须将所有节点都设置为已访问(第9行, visit_nodes() ),算法才能在达到目标节点后结束。 In effect, this sort of breaks out of the awaiting recursive calls since it ensures no other nodes are added to the path. 实际上,这种中断脱离了等待的递归调用,因为它确保没有其他节点添加到路径中。 My question is: 我的问题是:

Is there a cleaner/better way to do this? 有没有更清洁/更好的方法来做到这一点?

The solution seems a bit kludgy. 解决方案似乎有点混乱。 I'd appreciate any help. 我将不胜感激。

It would be better not to clutter the graph structure with visited information, as that really is context-sensitive information linked to a search algorithm, not with the graph itself. 最好不要将访问过的信息弄乱图形结构,因为这实际上是与搜索算法相关联的上下文相关信息,而不是图形本身。 You can use a separate set instead. 您可以使用单独的集。

Secondly, you have a bug in the code, as you keep adding to the path variable, even if your recursive call did not find the target node. 其次,即使您递归调用未找到目标节点,也一直在添加到path变量中,因此代码中存在一个错误。 So your path will even have nodes in sequence that have no edge between them, but are (close or remote) siblings/cousins. 因此,您的路径甚至将按顺序包含节点,它们之间没有边,而是(近端或远端)兄弟姐妹/表兄弟。

Instead you should only return a path when you found the target node, and then after making the recursive call you should test that condition to determine whether to prefix that path with the current edge node you are trying with. 相反,您应该仅在找到目标节点时返回路径,然后进行递归调用后,应测试该条件以确定是否在要尝试使用的当前边缘节点之前添加该路径的前缀。

There is in fact no need to keep a path variable, as per recursion level you are only looking for one node to be added to a path you get from the recursive call. 实际上,不需要保留路径变量,因为在递归级别上,您只想将一个节点添加到从递归调用获得的路径中。 It is not necessary to store that one node in a list. 不必将一个节点存储在列表中。 Just a simple variable will do. 只需一个简单的变量即可。

Here is the suggested code (not tested): 这是建议的代码(未经测试):

def dfs(G, v):
    visited = set() # keep visited information away from graph

    def _dfs(v):
        visited.add(v) # mark the node as visited
        if v.is_goal:
            return [':-)'] # return end point of path
        for edge in v.out_edges:
            neighbor = edge.to() # to avoid calling to() several times
            if neighbor not in visited:
                result = _dfs(neighbor)
                if result: # only when successful
                    # we only need 1 success: add current neighbor and exit
                    return [neighbor.label] + result 
                # otherwise, nothing should change to any path: continue

        # don't return anything in case of failure

    # call nested function: the visited and Graph variables are shared
    return _dfs(v) 

Remark 备注

For the same reason as for visited , it is maybe better to remove the is_goal marking from the graph as well, and pass that target node as an additional argument to the dfs function. 出于同样的原因,作为visited ,它也许是更好的,以除去is_goal从图中标记为好,并通过该目标节点作为附加参数的dfs功能。

It would also be nice to give a default value for the weight argument, so that you can use this code for unweighted graphs as well. weight参数提供默认值也很不错,这样您也可以将此代码用于未加权图。

See how it runs on a sample graph with 5 nodes on repl.it . 查看它如何在repl.it上具有5个节点的示例图上运行

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

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