Làm cách nào để bạn theo dõi đường dẫn của Tìm kiếm theo chiều rộng-Đầu tiên, chẳng hạn như trong ví dụ sau:
Nếu đang tìm kiếm khóa 11
, hãy trả về danh sách ngắn nhất nối từ 1 đến 11.
[1, 4, 7, 11]
Làm cách nào để bạn theo dõi đường dẫn của Tìm kiếm theo chiều rộng-Đầu tiên, chẳng hạn như trong ví dụ sau:
Nếu đang tìm kiếm khóa 11
, hãy trả về danh sách ngắn nhất nối từ 1 đến 11.
[1, 4, 7, 11]
Câu trả lời:
Trước tiên, bạn nên xem tại http://en.wikipedia.org/wiki/Breadth-first_search .
Dưới đây là một triển khai nhanh chóng, trong đó tôi đã sử dụng một danh sách danh sách để đại diện cho hàng đợi các đường dẫn.
# 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')
Một cách tiếp cận khác sẽ là duy trì một ánh xạ từ mỗi nút đến nút cha của nó và khi kiểm tra nút liền kề, hãy ghi lại nút cha của nó. Khi tìm kiếm xong, chỉ cần truy xuất ngược theo ánh xạ gốc.
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')
Các mã trên dựa trên giả định rằng không có chu kỳ.
Tôi thích câu trả lời đầu tiên của qiao rất nhiều! Điều duy nhất còn thiếu ở đây là đánh dấu các đỉnh là đã thăm.
Tại sao chúng ta cần làm điều đó?
Hãy tưởng tượng rằng có một nút số 13 khác được kết nối từ nút 11. Bây giờ mục tiêu của chúng ta là tìm nút 13.
Sau khi chạy một chút, hàng đợi sẽ trông như thế này:
[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]
Lưu ý rằng có HAI đường dẫn với nút số 10 ở cuối.
Có nghĩa là các đường dẫn từ nút số 10 sẽ được kiểm tra hai lần. Trong trường hợp này, nó trông không tệ lắm vì nút số 10 không có con nào .. Nhưng nó có thể thực sự tồi tệ (thậm chí ở đây chúng tôi sẽ kiểm tra nút đó hai lần mà không có lý do gì ..)
Nút số 13 không ở trong những đường dẫn đó để chương trình sẽ không quay lại trước khi đến đường dẫn thứ hai có nút số 10 ở cuối..Và chúng tôi sẽ kiểm tra lại ..
Tất cả những gì chúng tôi còn thiếu là một bộ để đánh dấu các nút đã truy cập và không phải kiểm tra lại chúng ..
Đây là mã của qiao sau khi sửa đổi:
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)
Đầu ra của chương trình sẽ là:
[1, 4, 7, 11, 13]
Nếu không có sự kiểm tra lại ma thuật ..
collections.deque
cho queue
như list.pop (0) phải chịu O(n)
phong trào nhớ. Ngoài ra, vì lợi ích của hậu thế, nếu bạn muốn làm DFS, chỉ cần đặt path = queue.pop()
trong trường hợp đó biến queue
thực sự hoạt động giống như a stack
.
Mã rất dễ dàng. Bạn tiếp tục thêm đường dẫn mỗi khi bạn phát hiện ra một nút.
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]))
Tôi nghĩ tôi sẽ thử viết mã này cho vui:
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
Nếu bạn muốn chu kỳ, bạn có thể thêm cái này:
for i, j in for_front: # allow cycles, add this code
if i in graph:
del graph[i]
Tôi thích cả câu trả lời đầu tiên của @Qiao và phần bổ sung của @ Or. Để tiện cho việc xử lý bớt một chút, tôi muốn thêm vào câu trả lời của Or.
Trong câu trả lời của @ Or, việc theo dõi nút đã truy cập là rất tốt. Chúng tôi cũng có thể cho phép chương trình thoát sớm hơn so với hiện tại. Tại một thời điểm nào đó trong vòng lặp for, giá trị current_neighbour
sẽ phải là end
, và khi điều đó xảy ra, con đường ngắn nhất sẽ được tìm thấy và chương trình có thể trả về.
Tôi sẽ sửa đổi phương thức như sau, hãy chú ý đến vòng lặp 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)
Đầu ra và mọi thứ khác sẽ giống nhau. Tuy nhiên, mã sẽ mất ít thời gian hơn để xử lý. Điều này đặc biệt hữu ích trên các đồ thị lớn hơn. Tôi hy vọng điều này sẽ giúp ai đó trong tương lai.