如何追踪广度优先搜索中的路径?

发布于 2024-12-27 21:55:39 字数 277 浏览 6 评论 0原文

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

如果搜索键11,则返回连接的最短列表1 至 11。

[1, 4, 7, 11]

How do you trace the path of a Breadth-First Search, such that in the following example:

If searching for key 11, return the shortest list connecting 1 to 11.

[1, 4, 7, 11]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

小草泠泠 2025-01-03 21:55:39

您应该查看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')

打印:['1', '4', '7', '11']


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

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')

上面的代码基于没有循环的假设。

You should have look at http://en.wikipedia.org/wiki/Breadth-first_search first.


Below is a quick implementation, in which I used a list of list to represent the queue of paths.

# 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')

This prints: ['1', '4', '7', '11']


Another approach would be maintaining a mapping from each node to its parent, and when inspecting the adjacent node, record its parent. When the search is done, simply backtrace according the parent mapping.

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')

The above codes are based on the assumption that there's no cycles.

溇涏 2025-01-03 21:55:39

非常简单的代码。每次发现节点时,您都​​会不断附加路径。

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]))

Very easy code. You keep appending the path each time you discover a node.

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]))
新雨望断虹 2025-01-03 21:55:39

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

为什么我们需要这样做?
假设有另一个节点 13 从节点 11 连接。现在我们的目标是找到节点 13。
运行一段时间后,队列将如下所示:

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]

请注意,末尾有两条节点号为 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)

程序的输出将是:

[1, 4, 7, 11, 13]

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

I liked qiao's first answer very much!
The only thing missing here is to mark the vertexes as visited.

Why we need to do it?
Lets imagine that there is another node number 13 connected from node 11. Now our goal is to find node 13.
After a little bit of a run the queue will look like this:

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]

Note that there are TWO paths with node number 10 at the end.
Which means that the paths from node number 10 will be checked twice. In this case it doesn't look so bad because node number 10 doesn't have any children.. But it could be really bad (even here we will check that node twice for no reason..)
Node number 13 isn't in those paths so the program won't return before reaching to the second path with node number 10 at the end..And we will recheck it..

All we are missing is a set to mark the visited nodes and not to check them again..
This is qiao's code after the modification:

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)

The output of the program will be:

[1, 4, 7, 11, 13]

Without the unneccecery rechecks..

深居我梦 2025-01-03 21:55:39

我想我应该尝试编写这个代码来娱乐:

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

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

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]

I thought I'd try code this up for fun:

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

If you want cycles you could add this:

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]
一念一轮回 2025-01-03 21:55:39

如果图表中包含了循环,这样的效果不是更好吗?

from collections import deque

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


def bfs1(graph_to_search, start, end):
    queue = deque([start])
    visited = {start}
    trace = {}

    while queue:
        # Gets the first path in the queue
        vertex = queue.popleft()
        # Checks if we got to the end
        if vertex == end:
            break

        for neighbour in graph_to_search.get(vertex, []):
            # We check if the current neighbour is already in the visited nodes set in order not to re-add it
            if neighbour not in visited:
            # Mark the vertex as visited
                visited.add(neighbour)
                trace[neighbour] = vertex
                queue.append(neighbour)

path = [end]
while path[-1] != start:
    last_node = path[-1]
    next_node = trace[last_node]
    path.append(next_node)

return path[::-1]

print(bfs1(graph,1, 13))

这样只有新节点才会被访问,而且避免了循环。

With cycles included in the graph, would not something like this work better?

from collections import deque

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


def bfs1(graph_to_search, start, end):
    queue = deque([start])
    visited = {start}
    trace = {}

    while queue:
        # Gets the first path in the queue
        vertex = queue.popleft()
        # Checks if we got to the end
        if vertex == end:
            break

        for neighbour in graph_to_search.get(vertex, []):
            # We check if the current neighbour is already in the visited nodes set in order not to re-add it
            if neighbour not in visited:
            # Mark the vertex as visited
                visited.add(neighbour)
                trace[neighbour] = vertex
                queue.append(neighbour)

path = [end]
while path[-1] != start:
    last_node = path[-1]
    next_node = trace[last_node]
    path.append(next_node)

return path[::-1]

print(bfs1(graph,1, 13))

This way only new nodes will be visited and moreover, avoid cycles.

涙—继续流 2025-01-03 21:55:39

我喜欢@Qiao的第一个答案和@Or的补充。为了减少处理量,我想添加 Or 的答案。

在@Or 的回答中,跟踪访问的节点非常棒。我们还可以允许程序比当前更早退出。在 for 循环中的某个时刻,current_neighbour 必须是 end,一旦发生这种情况,就会找到最短路径,程序就可以返回。

我将修改该方法如下,密切注意 for 循环

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)

                #No need to visit other neighbour. Return at once
                if current_neighbour == end
                    return new_path;

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


print bfs(graph, 1, 13)

输出和其他一切都将是相同的。但是,处理代码所需的时间会更少。这对于较大的图尤其有用。我希望这对将来的人有帮助。

I like both @Qiao first answer and @Or's addition. For a sake of a little less processing I would like to add to Or's answer.

In @Or's answer keeping track of visited node is great. We can also allow the program to exit sooner that it currently is. At some point in the for loop the current_neighbour will have to be the end, and once that happens the shortest path is found and program can return.

I would modify the the method as follow, pay close attention to the for loop

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)

                #No need to visit other neighbour. Return at once
                if current_neighbour == end
                    return new_path;

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


print bfs(graph, 1, 13)

The output and everything else will be the same. However, the code will take less time to process. This is especially useful on larger graphs. I hope this helps someone in the future.

巡山小妖精 2025-01-03 21:55:39

Javascript 版本和搜索第一个/所有路径...

PS,带有 cylces 的图表效果很好。

您可以将其转换python,它很容易

function search_path(graph, start, end, exhausted=true, method='bfs') {
    // note. Javascript Set is ordered set...
    const queue = [[start, new Set([start])]]
    const visited = new Set()
    const allpaths = []
    const hashPath = (path) => [...path].join(',') // any path hashing method
    while (queue.length) {
        const [node, path] = queue.shift()
        // visited node and its path instant. do not modify it others place
        visited.add(node)
        visited.add(hashPath(path))
        for (let _node of graph.get(node) || []) {
            // the paths already has the node, loops mean nothing though.
            if (path.has(_node))
                continue;
            // now new path has no repeated nodes.
            let newpath = new Set([...path, _node])
            if (_node == end){
                allpaths.push(newpath)
                if(!exhausted) return allpaths; // found and return 
            }
            else {
                if (!visited.has(_node) || // new node till now
                   // note: search all possible including the longest path
                   visited.has(_node) && !visited.has(hashPath(newpath))
                ) { 
                    if(method == 'bfs')
                       queue.push([_node, newpath])
                    else{
                       queue.unshift([_node, newpath])
                    }
                }
            }
        }
    }
    return allpaths
}

输出,如下所示..

[
    [ 'A', 'C' ],
    [ 'A', 'E', 'C'],
    [ 'A', 'E', 'F', 'C' ] // including F in `A -> C`
]

Javascript version and search first/all paths ...

P.S, Graph with cylces works well.

Your can convert it to python , it's easy

function search_path(graph, start, end, exhausted=true, method='bfs') {
    // note. Javascript Set is ordered set...
    const queue = [[start, new Set([start])]]
    const visited = new Set()
    const allpaths = []
    const hashPath = (path) => [...path].join(',') // any path hashing method
    while (queue.length) {
        const [node, path] = queue.shift()
        // visited node and its path instant. do not modify it others place
        visited.add(node)
        visited.add(hashPath(path))
        for (let _node of graph.get(node) || []) {
            // the paths already has the node, loops mean nothing though.
            if (path.has(_node))
                continue;
            // now new path has no repeated nodes.
            let newpath = new Set([...path, _node])
            if (_node == end){
                allpaths.push(newpath)
                if(!exhausted) return allpaths; // found and return 
            }
            else {
                if (!visited.has(_node) || // new node till now
                   // note: search all possible including the longest path
                   visited.has(_node) && !visited.has(hashPath(newpath))
                ) { 
                    if(method == 'bfs')
                       queue.push([_node, newpath])
                    else{
                       queue.unshift([_node, newpath])
                    }
                }
            }
        }
    }
    return allpaths
}

output like this..

[
    [ 'A', 'C' ],
    [ 'A', 'E', 'C'],
    [ 'A', 'E', 'F', 'C' ] // including F in `A -> C`
]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文