Python 如何在广度优先搜索中跟踪路径?
如何跟踪广度优先搜索的路径,例如在以下示例中: 如果搜索键Python 如何在广度优先搜索中跟踪路径?,python,algorithm,graph,breadth-first-search,Python,Algorithm,Graph,Breadth First Search,如何跟踪广度优先搜索的路径,例如在以下示例中: 如果搜索键11,则返回连接1到11的最短列表 [1, 4, 7, 11] 你应该先看看 下面是一个快速实现,其中我使用了一个列表列表来表示路径队列 # graph is in adjacent list representation graph = { '1': ['2', '3', '4'], '2': ['5', '6'], '5': ['9', '10'], '4': ['
11
,则返回连接1到11的最短列表
[1, 4, 7, 11]
你应该先看看
下面是一个快速实现,其中我使用了一个列表列表来表示路径队列
# 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')
另一种方法是维护从每个节点到其父节点的映射,并在检查相邻节点时记录其父节点。搜索完成后,只需根据父映射进行回溯即可
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')
图形={
'1': ['2', '3', '4'],
'2': ['5', '6'],
'5': ['9', '10'],
'4': ['7', '8'],
'7': ['11', '12']
}
def回溯(父级、开始、结束):
路径=[结束]
而路径[-1]!=开始:
append(父[路径[-1]])
path.reverse()
返回路径
def bfs(图表、开始、结束):
父={}
队列=[]
queue.append(开始)
排队时:
节点=队列.pop(0)
如果节点==结束:
返回回溯(父级、开始、结束)
对于graph.get(节点,[])中的相邻节点:
如果节点不在队列中:
parent[nexting]=node#我想我应该试着编写这个代码来娱乐一下:
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]
我非常喜欢乔的第一个答案!
这里唯一缺少的是将顶点标记为已访问。
我们为什么要这样做?
让我们假设有另一个节点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)之前不会返回。我们将重新检查它。
我们缺少的只是一个标记已访问节点的集合,而不是再次检查它们。
这是乔修改后的代码:
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]
没有不必要的复查。我喜欢@Qiao的第一个答案和@Or的补充。为了少一点处理,我想补充Or的答案
在@Or的回答中,跟踪访问的节点非常好。我们还可以允许程序比当前更快地退出。在for循环的某个点上,当前_邻居
必须是结束
,一旦发生这种情况,就会找到最短路径,程序可以返回
我将修改方法如下,密切关注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)
输出和其他一切都将是相同的。但是,处理代码所需的时间会更少。这在较大的图上特别有用。我希望这对将来的人有所帮助。非常简单的代码。每次发现节点时,都会不断追加路径
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]))
在图表中包含了循环之后,类似这样的工作不是更好吗
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))
通过这种方式,只访问新节点,而且避免循环。这实际上是我几个月前根据凯文·培根定律帮助一位朋友完成的一项旧任务。我的最终解决方案非常草率,我基本上做了另一个广度优先搜索来“倒带”和回溯。我不想找到更好的解决办法。太好了。我想重新审视一个老问题,试图找到一个更好的答案是一个令人钦佩的特质在工程师。我祝你学业和事业顺利。谢谢你的表扬,我只是相信如果我现在不学习,我会再次面临同样的问题。这可能是最好的复制品!我的思维过程使我相信创建某种类型的表或矩阵,但我还没有学习图形。谢谢。我也尝试过使用回溯方法,虽然这看起来更干净。如果你只知道开始和结束,而不知道中间的节点,你能画一张图吗?或者甚至是图形以外的另一种方法?是否可以调整第一种算法,使其返回从1到11的所有路径(假设有多条路径)?@l19当您找到一条路径(node==end
)时,将该路径添加到另一个包含您找到的所有路径的列表中,然后继续
,而不是返回
。如果要使用访问集来防止循环,请永远不要将结束节点添加到访问集(否则只有一条路径可以有该结束节点)。建议使用collections.deque而不是列表。在为_前端构建下一个_之后,list.pop(0)的复杂度是O(n),而deque.popleft()的复杂度是O(1)。接下来的一个问题是,如果图中包含循环怎么办?例如,节点1是否有一条边连接回自身?如果图形在两个节点之间有多条边怎么办?使用collections.deque
forqueue
as list可能会很有用。pop(0)导致O(n)
内存移动。此外,为了子孙后代,如果要执行DFS,只需设置path=queue.pop()
,在这种情况下,变量queue
实际上就像一个stack
。这将重新访问相邻节点访问的节点,例如,在有三个节点1-2-3的情况下,它将访问1,将2添加到队列中,然后将1和3添加到队列中。如果顶点不在访问中
检查应该在for循环中,而不是在for循环之外。然后可以删除外部检查,因为如果节点已被访问,则不会向队列中添加任何内容。与其他答案相比,我发现您的代码非常可读。非常感谢你!