Як простежити шлях пошуку за шириною до першого, наприклад, у наступному прикладі:
Якщо ви шукаєте ключ 11
, поверніть найкоротший список, що з'єднує 1 до 11.
[1, 4, 7, 11]
Як простежити шлях пошуку за шириною до першого, наприклад, у наступному прикладі:
Якщо ви шукаєте ключ 11
, поверніть найкоротший список, що з'єднує 1 до 11.
[1, 4, 7, 11]
Відповіді:
Вам слід спочатку подивитися на 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')
Іншим підходом було б підтримання відображення від кожного вузла до його батьківського, і при огляді сусіднього вузла запишіть його батьківський. Коли пошук виконано, просто відхиліться відповідно до батьківського відображення.
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')
Вищеописані коди ґрунтуються на припущенні, що циклів немає.
Мені дуже сподобалась перша відповідь 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]
Без повторних перевірок.
collections.deque
для queue
list.pop (0), які здійснюють O(n)
рухи пам'яті. Також, для нащадків, якщо ви хочете зробити DFS, просто встановіть, path = queue.pop()
у цьому випадку змінна queue
насправді діє як stack
.
Дуже простий код. Ви продовжуєте додавати шлях щоразу, коли виявляєте вузол.
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]))
Я думав, що спробую це кодувати для розваги:
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]
Мені подобається перша відповідь @Qiao і додаток @ 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)
Вихід та все інше будуть однаковими. Однак на обробку коду знадобиться менше часу. Це особливо корисно для великих графіків. Я сподіваюся, що це допоможе комусь у майбутньому.