Comment tracer le chemin d'une recherche en largeur d'abord, comme dans l'exemple suivant:
Si vous recherchez une clé 11
, renvoyez la liste la plus courte reliant 1 à 11.
[1, 4, 7, 11]
Comment tracer le chemin d'une recherche en largeur d'abord, comme dans l'exemple suivant:
Si vous recherchez une clé 11
, renvoyez la liste la plus courte reliant 1 à 11.
[1, 4, 7, 11]
Réponses:
Vous devriez d'abord regarder http://en.wikipedia.org/wiki/Breadth-first_search .
Voici une implémentation rapide, dans laquelle j'ai utilisé une liste de liste pour représenter la file d'attente des chemins.
# 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')
Une autre approche consisterait à maintenir un mappage de chaque nœud à son parent et, lors de l'inspection du nœud adjacent, à enregistrer son parent. Lorsque la recherche est terminée, effectuez simplement un retour arrière en fonction du mappage parent.
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')
Les codes ci-dessus sont basés sur l'hypothèse qu'il n'y a pas de cycles.
J'ai beaucoup aimé la première réponse de qiao! La seule chose qui manque ici est de marquer les sommets comme visités.
Pourquoi devons-nous le faire?
Imaginons qu'il y ait un autre nœud numéro 13 connecté à partir du nœud 11. Maintenant, notre objectif est de trouver le nœud 13.
Après un peu de course, la file d'attente ressemblera à ceci:
[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]
Notez qu'il y a DEUX chemins avec le numéro de nœud 10 à la fin.
Ce qui signifie que les chemins du nœud numéro 10 seront vérifiés deux fois. Dans ce cas, cela n'a pas l'air si mal car le nœud numéro 10 n'a pas d'enfants .. Mais cela pourrait être vraiment mauvais (même ici, nous vérifierons ce nœud deux fois sans raison ..) Le
nœud numéro 13 n'est pas dans ces chemins afin que le programme ne revienne pas avant d'atteindre le deuxième chemin avec le noeud numéro 10 à la fin .. Et nous le revérifierons.
Il ne nous manque qu'un ensemble pour marquer les nœuds visités et ne pas les vérifier à nouveau.
Voici le code de qiao après la 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)
La sortie du programme sera:
[1, 4, 7, 11, 13]
Sans les revérifications inutiles.
collections.deque
pour queue
as list.pop (0) engendre O(n)
des mouvements de mémoire. De plus, dans un souci de postérité, si vous voulez faire DFS, définissez simplement path = queue.pop()
dans quel cas la variable queue
agit en fait comme un stack
.
Code très simple. Vous continuez à ajouter le chemin chaque fois que vous découvrez un nœud.
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]))
J'ai pensé essayer de coder ceci pour le plaisir:
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
Si vous voulez des cycles, vous pouvez ajouter ceci:
for i, j in for_front: # allow cycles, add this code
if i in graph:
del graph[i]
J'aime à la fois la première réponse de @Qiao et l'ajout de @ Or. Pour un peu moins de traitement, je voudrais ajouter à la réponse d'Or.
Dans la réponse de @ Or, le suivi du nœud visité est excellent. Nous pouvons également permettre au programme de se terminer plus tôt qu'il ne l'est actuellement. À un moment donné dans la boucle for, il current_neighbour
devra être le end
, et une fois que cela se produit, le chemin le plus court est trouvé et le programme peut revenir.
Je modifierais la méthode comme suit, faites très attention à la boucle 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)
La sortie et tout le reste seront les mêmes. Cependant, le code prendra moins de temps à traiter. Ceci est particulièrement utile sur les graphiques plus grands. J'espère que cela aidera quelqu'un à l'avenir.