所以我在StackOverflow中看到了关于Python中DFS算法的以下帖子(非常有帮助):
Does this python code employs Depth First Search (DFS) for finding all paths?
我还有一个需要分析的图表(找到两个节点之间的每条可能路径),但我还需要在那里包含周期。例如,如果我有这样的图表:
graph = {'Start': ['1'],
'1': ['2'],
'2': ['3','End'],
'3': ['2','End']}
我想得到以下输出:
Start, 1, 2, 3, End
Start, 1, 2, End
Start, 1, 2, 3, 2, End
Start, 1, 2, 3, 2, 3, End
有没有办法更改以下代码才能执行此操作?
def find_all_paths(graph, start, end, path=[]):
path = path + [start]
if start == end:
return [path]
if not graph.has_key(start):
return []
paths = []
for node in graph[start]:
if node not in path:
paths += find_all_paths(graph, node, end, path)
return paths
print find_all_paths(graph, 'Start', 'End')
答案 0 :(得分:4)
这不是你想用简单的Depth-First Search(DFS)做的事情。
DFS,深度优先,如果达到一个周期,将被阻止。 周期无限深。
如果要在包含循环时输出(可能使用生成器)每个节点的inifnite路径,则应使用Breadth-First Search(BFS)。作为广度优先,意味着一个循环不会阻止它到达其他路径。
这里的“让步”是广度优先搜索消耗更多内存,在运行期间保留更多列表。如果这是一个问题,您应该使用DFS's iterative deepening solution。
简单的BFS解决方案:
使用Queue
(使用队列可以轻松实现BFS,使用堆栈的DF,如您所见here):
#!/usr/bin/env python
import Queue
graph = {'Start': ['1'],
'1': ['2'],
'2': ['3','End'],
'3': ['2','End']}
expand_queue = Queue.Queue()
def BFS_generator(graph, start, end, path):
# initialize generator
expand_queue.put((graph, start, end, path))
while not expand_queue.empty():
graph, current, end, path = expand_queue.get()
if current == end:
# route done - yield result
yield path + [current]
if current in graph:
# skip neighbor-less nodes
for neighbor in graph[current]:
# put all neighbors last in line to expand
expand_queue.put((graph, neighbor, end, path + [current]))
gen = BFS_generator(graph, "Start", "End", [])
# get only 10 first paths
for _ in xrange(10):
print next(gen)
输出:
['Start', '1', '2', 'End']
['Start', '1', '2', '3', 'End']
['Start', '1', '2', '3', '2', 'End']
['Start', '1', '2', '3', '2', '3', 'End']
['Start', '1', '2', '3', '2', '3', '2', 'End']
['Start', '1', '2', '3', '2', '3', '2', '3', 'End']
['Start', '1', '2', '3', '2', '3', '2', '3', '2', 'End']
['Start', '1', '2', '3', '2', '3', '2', '3', '2', '3', 'End']
['Start', '1', '2', '3', '2', '3', '2', '3', '2', '3', '2', 'End']
['Start', '1', '2', '3', '2', '3', '2', '3', '2', '3', '2', '3', 'End']
通过迭代深化将解决方案推广到DFS和BFS:
您还可以使用更通用的代码,使用Queue
或Stack
,然后使用BFS(队列)或DFS(堆栈)轻松获得迭代解决方案。取决于你需要什么。
首先创建一个Stack
类(出于界面目的,我们需要list
):
class Stack():
def __init__(self):
self.stack = []
def get(self):
return self.stack.pop(0)
def put(self, item):
self.stack.insert(0, item)
def empty(self):
return len(self.stack) == 0
现在您同时拥有Queue
和堆栈,如果我们更改所使用的数据结构,那么算法的简单推广,使其成为迭代和数据结构不可知,将为我们提供两种解决方案:
def iterative_search(data_structure, graph, start, end, limit=None):
# initialize generator
data_structure.put((graph, start, end,[]))
while not data_structure.empty():
graph, current, end, path = data_structure.get()
if limit and len(path) > limit:
continue
if current == end:
# route done - yield result
yield tuple(path + [current])
if current in graph:
# skip neighbor-less nodes
for neighbor in graph[current]:
# store all neighbors according to data structure
data_structure.put(
(graph, neighbor, end, path + [current])
)
将整个事情放在一起:
我们可以看到他们选择了不同的路线(我已经改变了graph
以使其更有趣):
import Queue
class Stack():
def __init__(self):
self.stack = []
def get(self):
return self.stack.pop(0)
def put(self, item):
self.stack.insert(0, item)
def empty(self):
return len(self.stack) == 0
graph = {'Start': ['1'],
'1': ['2'],
'2': ['3','End'],
'3': ['2', '4','End'],
'4': ['3']}
def iterative_search(data_structure, graph, start, end, limit=None):
# initialize generator
data_structure.put((graph, start, end,[]))
while not data_structure.empty():
graph, current, end, path = data_structure.get()
# make solution depth limited
# makes it iterative - for DFS to use all paths
if limit and len(path) > limit:
continue
if current == end:
# route done - yield result
yield tuple(path + [current])
if current in graph:
# skip neighbor-less nodes
for neighbor in graph[current]:
# store all neighbors according to data structure
data_structure.put(
(graph, neighbor, end, path + [current])
)
现在我们已经为iteratice DFS和BFS提供了一个通用函数,我们可以比较它们提供的解决方案:
import os
# bfs - using queue
gen = iterative_search(Queue.Queue(), graph, "Start", "End")
print "BFS"
# get only 10 first paths
bfs_path_set = set()
while len(bfs_path_set) < 10:
bfs_path_set.add(next(gen))
print os.linesep.join(map(str, bfs_path_set))
print "Iterative DFS"
# dfs - using stack
gen = iterative_search(Stack(), graph, "Start", "End", limit=5)
# get only 10 first paths
dfs_path_set = set()
limit = 1
while len(dfs_path_set) < 10:
try:
dfs_path_set.add(next(gen))
except StopIteration:
limit += 1
print "depth limit reached, increasing depth limit to %d" % limit
gen = iterative_search(
Stack(), graph, "Start", "End", limit=limit
)
print os.linesep.join(map(str, dfs_path_set))
print "difference BFS - DFS: %s" % str(bfs_path_set - dfs_path_set)
print "difference DFS - BFS: %s" % str(dfs_path_set - bfs_path_set)
输出:
BFS
('Start', '1', '2', '3', '2', '3', '4', '3', 'End')
('Start', '1', '2', '3', '2', '3', '2', '3', 'End')
('Start', '1', '2', '3', '2', '3', '2', 'End')
('Start', '1', '2', '3', '4', '3', '2', 'End')
('Start', '1', '2', '3', '4', '3', 'End')
('Start', '1', '2', '3', 'End')
('Start', '1', '2', '3', '4', '3', '2', '3', 'End')
('Start', '1', '2', '3', '2', '3', 'End')
('Start', '1', '2', '3', '2', 'End')
('Start', '1', '2', 'End')
Iterative DFS
limit reached, increasing limit to 2
limit reached, increasing limit to 3
limit reached, increasing limit to 4
limit reached, increasing limit to 5
limit reached, increasing limit to 6
limit reached, increasing limit to 7
limit reached, increasing limit to 8
('Start', '1', '2', '3', '2', '3', '4', '3', 'End')
('Start', '1', '2', '3', '4', '3', '4', '3', 'End')
('Start', '1', '2', '3', '2', '3', '2', 'End')
('Start', '1', '2', '3', '4', '3', '2', 'End')
('Start', '1', '2', '3', '4', '3', 'End')
('Start', '1', '2', '3', 'End')
('Start', '1', '2', '3', '4', '3', '2', '3', 'End')
('Start', '1', '2', '3', '2', '3', 'End')
('Start', '1', '2', '3', '2', 'End')
('Start', '1', '2', 'End')
difference BFS - DFS: set([('Start', '1', '2', '3', '2', '3', '2', '3', 'End')])
difference DFS - BFS: set([('Start', '1', '2', '3', '4', '3', '4', '3', 'End')])
备注:强>
解决方案的差异:您可以看到解决方案的路径选择各不相同。您可以通过将解决方案的长度设置为11
而不是10
来验证它们最终获得所有路径(这将使两个解决方案集完全相同 - 因为它们将使用所有9个长度的解决方案)。 / p>
内存消耗:值得注意的是,DFS的这种实现并不是最佳的,因为它确实存储了节点的所有邻居。它应该通常比BFS更好,它在使用之前存储更多的邻居,但是应该存在使用backtracking和递归的更优化的解决方案。
答案 1 :(得分:2)
如评论中所述,如果允许任意循环,您的算法没有理由终止。您可以做的是允许最大路径长度并关闭所有太长的路径:
def find_all_paths(graph, start, end, path=[], max_length=10):
if len(path) >= max_length:
return []
path = path + [start]
if start == end:
return [path]
if start not in graph:
return []
paths = []
for node in graph[start]:
paths += find_all_paths(graph, node, end, path, max_length=max_length)
return paths
带
graph = {'Start': ['1'],
'1': ['2'],
'2': ['3','End'],
'3': ['2','End']}
print find_all_paths(graph, 'Start', 'End', max_length=10)
您的算法打印:
[['Start', '1', '2', '3', '2', '3', '2', '3', '2', 'End'], ['Start', '1', '2', '3', '2', '3', '2', '3', 'End'], ['Start', '1', '2', '3', '2', '3', '2', 'End'], ['Start', '1', '2', '3', '2', '3', 'End'], ['Start', '1', '2', '3', '2', 'End'], ['Start', '1', '2', '3', 'End'], ['Start', '1', '2', 'End']]
修改:
用更加pythonic not graph.has_key
start not in graph