如何在广度优先搜索中跟踪路径?

Chr*_*eta 90 python algorithm graph breadth-first-search

如何跟踪广度优先搜索的路径,以便在以下示例中:

如果要搜索密钥11,请返回连接1到11 的最短列表.

[1, 4, 7, 11]
Run Code Online (Sandbox Code Playgroud)

qia*_*iao 166

您应该首先查看http://en.wikipedia.org/wiki/Breadth-first_search.


下面是一个快速实现,其中我使用列表列表来表示路径队列.

# graph is in adjacent list representation
graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, start, end):
    # maintain a queue of paths
    queue = []
    # push the first path into the queue
    queue.append([start])
    while queue:
        # get the first path from the queue
        path = queue.pop(0)
        # get the last node from the path
        node = path[-1]
        # path found
        if node == end:
            return path
        # enumerate all adjacent nodes, construct a new path and push it into the queue
        for adjacent in graph.get(node, []):
            new_path = list(path)
            new_path.append(adjacent)
            queue.append(new_path)

print bfs(graph, '1', '11')
Run Code Online (Sandbox Code Playgroud)

另一种方法是维护从每个节点到其父节点的映射,并在检查相邻节点时记录其父节点.搜索完成后,只需根据父映射进行回溯.

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def backtrace(parent, start, end):
    path = [end]
    while path[-1] != start:
        path.append(parent[path[-1]])
    path.reverse()
    return path


def bfs(graph, start, end):
    parent = {}
    queue = []
    queue.append(start)
    while queue:
        node = queue.pop(0)
        if node == end:
            return backtrace(parent, start, end)
        for adjacent in graph.get(node, []):
            if node not in queue :
                parent[adjacent] = node # <<<<< record its parent 
                queue.append(adjacent)

print bfs(graph, '1', '11')
Run Code Online (Sandbox Code Playgroud)

上述代码基于没有循环的假设.

  • 这太棒了!我的思维过程让我相信创建某种类型的表格或矩阵,我还没有学习图形.谢谢. (2认同)
  • 是否可以调整第一个算法,使其返回从 1 到 11 的“所有”路径(假设有多个路径)? (2认同)
  • 建议使用 collections.deque 而不是列表。list.pop(0) 的复杂度是 O(n) 而 deque.popleft() 是 O(1) (2认同)

Or *_*zaz 20

我非常喜欢乔的第一个答案!这里唯一缺少的是将顶点标记为已访问.

我们为什么需要这样做?
让我们想象一下,从节点11连接了另一个节点号13.现在我们的目标是找到节点13.
经过一段时间的运行后,队列将如下所示:

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]
Run Code Online (Sandbox Code Playgroud)

请注意,末尾有两个节点号为10的路径.
这意味着将检查节点号10的路径两次.在这种情况下,它看起来并不那么糟糕,因为节点号10没有任何子节点..但它可能非常糟糕(即使在这里我们将无缘无故地检查该节点两次..)
节点号13不在那些路径所以程序在到达第二个路径之前不会返回,最后节点号为10 ..我们将重新检查它.

我们所缺少的是一个标记被访问节点的集合,而不是再次检查它们.
这是修改后的qiao代码:

graph = {
    1: [2, 3, 4],
    2: [5, 6],
    3: [10],
    4: [7, 8],
    5: [9, 10],
    7: [11, 12],
    11: [13]
}


def bfs(graph_to_search, start, end):
    queue = [[start]]
    visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)
Run Code Online (Sandbox Code Playgroud)

该计划的输出将是:

[1, 4, 7, 11, 13]
Run Code Online (Sandbox Code Playgroud)

没有不必要的重新检查..

  • 使用`collections.deque`作为`queue`作为list.pop(0)引起'O(n)`记忆运动可能是有用的.另外,为了后人,如果你想做DFS,只需设置`path = queue.pop()`,在这种情况下,变量`queue`实际上就像`stack`. (5认同)

rob*_*ing 8

我以为我会尝试编写这个有趣的代码:

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, forefront, end):
    # assumes no cycles

    next_forefront = [(node, path + ',' + node) for i, path in forefront if i in graph for node in graph[i]]

    for node,path in next_forefront:
        if node==end:
            return path
    else:
        return bfs(graph,next_forefront,end)

print bfs(graph,[('1','1')],'11')

# >>>
# 1, 4, 7, 11
Run Code Online (Sandbox Code Playgroud)

如果你想要循环,你可以添加:

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]
Run Code Online (Sandbox Code Playgroud)

  • +1,你提醒我一件事:`假设没有循环`:) (4认同)

Sea*_*hot 7

非常简单的代码.每次发现节点时都会继续附加路径.

graph = {
         'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])
         }
def retunShortestPath(graph, start, end):

    queue = [(start,[start])]
    visited = set()

    while queue:
        vertex, path = queue.pop(0)
        visited.add(vertex)
        for node in graph[vertex]:
            if node == end:
                return path + [end]
            else:
                if node not in visited:
                    visited.add(node)
                    queue.append((node, path + [node]))
Run Code Online (Sandbox Code Playgroud)

  • 与其他答案相比,我发现您的代码非常易读。非常感谢! (2认同)