你没有回溯机制getCurPoint()
。这意味着您的算法正在使用找到的第一条在图中行进的边,构建一条可能导致死胡同的路径,而无需遍历所有节点。
这正是您的示例所发生的情况。您的算法将从 node 开始0
到 node 1
。此节点提供 3 条边来继续您的旅行(分别是(1, 5)
、(1, 7)
、(1, 6)
),但其中一条边会导致死胡同,而无法完成欧拉之旅。不幸的是,您的图形定义中列出的第一条边(1, 5)
是错误的路径,不会让您有任何机会到达节点6
、3
和7
。
要检查我的确认,您可以尝试更改图的给定定义以用 反转边(1, 5)
,(1, 7)
然后查看您的算法将如何正确列出所有节点。
我该如何帮助
首先,为了帮助您自己解决这些问题,您应该始终制作图表(不仅是为了让费曼开心),并尝试在图表上遵循您的算法。这些案例很容易得出,很明显算法不正确,您可能会发现原因。
其次,这个练习是关于回溯的。你知道这是什么吗 ?如果没有,你可以尝试用谷歌搜索这个词,甚至在 stackoverflow 搜索框中搜索它,维基百科也有一篇很好的文章。
最后,我可以给你一些关于你的编码风格的建议。请将它们视为个人信息,并采取似乎适合您当前需求的方式:
如果可能:
for x in range(len(graph)):
如果您以后才使用,请避免使用graph[x]
。Python 提供了一个非常优雅的替换解决方案for edge in graph:
。如果你真的需要你正在迭代的元素的索引,你可以选择优雅的:
for i, edge in enumerate(graph):
避免你的 3 行构造(你已经使用过两次)涉及 aif
中
的孤独for
:
for x in criticals[1]:
if x != point:
stops.append(x)
更喜欢显式和更小:
node_a, node_b = critical_edges[1]
stops += [node_a] if node_b == node else [node_b]
不太重要的代码装饰风格备注:
- 避免 java CamelCase 用于变量、函数名和方法名,python 在样式上有一个
PEP8,它更喜欢变量名使用带下划线的小写字母。
- 将“point”重命名为“node”以坚持图形上更广泛使用的词汇
- 将 'pair' 重命名为 'edge' 以获得对变量含义而不是其类型的描述性命名(尽管您可以考虑将这两种信息混合在所谓的Hungarian Notation中)
应用装饰性备注,例如,我们将getCurPoint
重命名为get_next_nodes
,其中:
- variable_with_underscore 而不是 CamelCase 和
Point
->nodes
如前所述,
- 复数,因为它将返回节点列表
- 'cur' 被重命名为'next',因为它是关于获取下一个节点。
以更 Python 的方式重写代码的示例
请注意,这只是一个重写,并且之前的错误始终存在。看看get_next_nodes()
函数是如何变化的:
def get_next_nodes(edges, cur_node):
connected_edges = [x for x in edges
if cur_node in x]
if connected_edges:
a, b = connected_edges[0]
next_node = b if a == cur_node else a
edges.remove(connected_edges[0])
return [cur_node] + get_next_nodes(edges, next_node)
return []
def take_tour(graph):
start_node = graph[0][0]
critical_edges = []
nodes = []
for edge in graph:
if start_node in edge and len(critical_edges) < 2:
critical_edges.append(edge)
else:
nodes.append(edge)
second_node = critical_edges[0][1]
stops = [start_node] + get_next_nodes(nodes, second_node)
a, b = critical_edges[1]
stops += [a] if b == start_node else [b]
return stops + [start_node]
现在,您的算法还有更多问题
您的脚本应该如何对非欧拉图做出反应?您的算法将输出各种各样的结果,从错误的节点列表到令人困惑的异常。如果要避免异常,则应引发适当的非混淆异常。用这些图试试你的算法:
非欧拉:
[(0, 1), (1, 5), (1, 7), (4, 5), (4, 8), (1, 6), (3, 7), (5, 9), ( 2, 4), (2, 5), (3, 6), (8, 9)]
非欧拉:[(0, 1), (0, 3)]
- 欧拉:
[(0, 0)]
- 非欧拉:
[]
您在列表中手动添加 3 个节点take_tour
。有一个更优雅的解决方案,代码更少!
你为什么选择一个结束边take_tour
和起始边?你可能选错了!您看到选择多项选择中的第一个可能是错误的。试试这个图表:
- 欧拉:
[(0, 1), (0, 1), (0, 2), (0, 2)]
,正确的结果应该是[0, 1, 0, 2, 0]
。
最后,让遇见给你一个正确的答案
这个简单的递归代码将回答您尝试解决的初始问题。
请注意初始getCurPoint
位置离做一些回溯不远。唯一的补充是引入了for
循环,它将解析所有可能的解决方案,而不是盲目地采用第一个解决方案。
但要允许回溯,该函数必须能够退出当前路径计算。这是通过False
在找不到路径的情况下允许返回值来完成的。False
然后将从子函数调用到父函数重新执行,有效地解开递归,直到它可以尝试另一个边缘,这要归功于for
循环。
您会注意到您可以合并getCurPoint
和take_tour
。这增加了在 new 中隐藏 2 个功能的好处take_tour
:
- 它将能够向您展示欧拉路径(这与必须在同一节点上开始和结束的欧拉之旅不同)。
- 如果您足够好奇以强制另一个起始节点查看路径,您可以提供一个起始节点。
这是代码:
def take_tour(graph, node_start=None, cycle_only=True):
if len(graph) == 0:
if node_start is None:
return []
return [node_start]
node_start = graph[0][0] if node_start is None else node_start
for chosen_edge in [x for x in graph if node_start in x]:
(node_a, node_b) = chosen_edge
path = take_tour([e for e in graph if e != chosen_edge],
node_b if node_start == node_a else node_a,
cycle_only=False)
if path is not False and (not cycle_only or node_start == path[-1]):
return [node_start] + path
return False
如果您正在寻找其他一些简单的练习,这里有 2 个简单到中等的问题,这些问题都悬而未决,可以一口气回答:
- 您将如何修改
take_tour
以返回所有可能的游览/路径的列表?
- 并且您能否
take_tour
在所有可能的游览/路径的迭代器中进行转换?(一个聪明的迭代器,只根据请求计算下一条路径)。
我希望这已经足够具有教育意义了。