Project Euler问题#18 Python-得到错误的结果。为什么?
在下班后的最后几天里,我试图解决Euler项目,作为学习Python的练习,现在我在 我研究了这个问题,认为可以使用Dijkstra算法解决,将节点的值作为负整数,从而找到“最长”路径 我的解决方案似乎几乎是正确的(我得到1068),也就是说是错的。它打印了一条路径,但据我所知,它不是正确的路径。但从一段时间以来我一直在看,我不知道为什么 也许这个问题不能用我的方法解决,我需要一些其他的方法,比如动态规划——或者也许我对Dijkstra的实现是错误的 我很有信心从文件到图形的解析是按预期进行的 这是数据集:Project Euler问题#18 Python-得到错误的结果。为什么?,python,algorithm,Python,Algorithm,在下班后的最后几天里,我试图解决Euler项目,作为学习Python的练习,现在我在 我研究了这个问题,认为可以使用Dijkstra算法解决,将节点的值作为负整数,从而找到“最长”路径 我的解决方案似乎几乎是正确的(我得到1068),也就是说是错的。它打印了一条路径,但据我所知,它不是正确的路径。但从一段时间以来我一直在看,我不知道为什么 也许这个问题不能用我的方法解决,我需要一些其他的方法,比如动态规划——或者也许我对Dijkstra的实现是错误的 我很有信心从文件到图形的解析是按预期进行的
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
这是代码。只要包含上述内容的文件路径正确,它就是一个完整的“工作示例”
class Graph:
def __init__(self):
self.nodes = []
self.edges = []
def add_node(self, node):
self.nodes.append(node)
def add_edge(self, edge):
self.edges.append(edge)
def edges_to_node(self, n):
edges = [edge for edge in self.edges if edge.node1.id == n.id]
return edges
class Node:
def __init__(self, id, value, goal):
self.id = id
self.value = value
self.goal = goal
self.visited = False
self.distance = 10000
self.previous = None
def __str__(self):
return "{} - {}".format(self.value, self.goal)
def __repr__(self):
return "{} - {}".format(self.value, self.goal)
class Edge:
def __init__(self, node1, node2):
self.node1 = node1
self.node2 = node2
f = open("problem18.data", "r")
content = f.read()
lines = content.split("\n")
data = []
graph = Graph()
index_generator = 1
last_line = len(lines) - 1
for i in range(len(lines)):
data.append([])
numbers = lines[i].split()
for number in numbers:
goal = i == last_line
data[-1].append(Node(index_generator, -int(number), goal))
index_generator += 1
for i in range(len(data)):
for j in range(len(data[i])):
node = data[i][j]
graph.add_node(node)
if i != last_line:
node2 = data[i+1][j]
node3 = data[i+1][j+1]
edge1 = Edge(node, node2)
edge2 = Edge(node, node3)
graph.add_edge(edge1)
graph.add_edge(edge2)
def dijkstra(graph, start):
start.distance = 0
queue = [start]
while len(queue):
queue.sort(key=lambda x: x.value, reverse=True)
current = queue.pop()
current.visited = True
if current.goal:
return reconstrcut_path(start, current)
edges = graph.edges_to_node(current)
for edge in edges:
neighbour = edge.node2
if neighbour.visited:
continue
queue.append(neighbour)
new_distance = current.distance + neighbour.value
if new_distance < neighbour.distance:
neighbour.distance = new_distance
neighbour.previous = current
return []
def reconstrcut_path(start, n):
path = []
current = n
while current.id is not start.id:
path.append(current)
current = current.previous
path.append(start)
return path
path = dijkstra(graph, graph.nodes[0])
tally = 0
for node in path:
number = max(node.value, -node.value)
print(number)
tally += number
print(tally)
事实上,动态规划可以很好地解决这个问题。我对这个和问题67的解决方案少于20行 这里的重点是Dijkstra方法:沿着三角形向下,在每个节点保持最大路径成本。第1行很简单:
75
第2行也很简单,因为两个值都是端点:每个值只有一个可能的路径:
95+75 64+75
评估结果是
170 139
第3行有两个端点,但中间值给出了关键逻辑:保留两条路径中较大的一条:
17+170 47+max(170, 139) 82+139
187 217 221
第4行有两个中间点。。。只要继续进行这个过程:
18+187 35+max(187, 217) 87+max(217, 221) 10+221
205 252 308 231
你能从这里拿走吗
作为检查,正确答案与您最初得到的答案非常接近
您的解决方案失败,因为您没有应用Dijkstra算法。这要求您维护到搜索中到达的每个节点的最佳路径。相反,您使用了一种逐行贪婪算法:在整个过程中,您只保留了迄今为止最好的路径 具体来说,当您在最下面一行的右侧附近找到
98
时,您强制假设它是最佳路径的一部分。你一行一行地继续。该数据集专门配置为使该方法失败。最佳路径从93+73+58
序列开始
你必须记住所有的道路;有一条路径不是最下面的一对行的总和,而是在中间行中捕获,而“胖”路径在中间会有一些较低的数字挨饿。 < P>实际上,动态编程会将这一点完全消除。我对这个和问题67的解决方案少于20行 这里的重点是Dijkstra方法:沿着三角形向下,在每个节点保持最大路径成本。第1行很简单:
75
第2行也很简单,因为两个值都是端点:每个值只有一个可能的路径:
95+75 64+75
评估结果是
170 139
第3行有两个端点,但中间值给出了关键逻辑:保留两条路径中较大的一条:
17+170 47+max(170, 139) 82+139
187 217 221
第4行有两个中间点。。。只要继续进行这个过程:
18+187 35+max(187, 217) 87+max(217, 221) 10+221
205 252 308 231
你能从这里拿走吗
作为检查,正确答案与您最初得到的答案非常接近
您的解决方案失败,因为您没有应用Dijkstra算法。这要求您维护到搜索中到达的每个节点的最佳路径。相反,您使用了一种逐行贪婪算法:在整个过程中,您只保留了迄今为止最好的路径 具体来说,当您在最下面一行的右侧附近找到
98
时,您强制假设它是最佳路径的一部分。你一行一行地继续。该数据集专门配置为使该方法失败。最佳路径从93+73+58
序列开始
你必须记住所有的道路;有一条路径不是最下面的一对行的总和,而是在中间行中捕获,而“胖”路径在中间会有一些较低的数字挨饿。 < P>考虑这个备选数据集:
01
00 01
00 00 01
00 00 00 01
99 00 00 00 01
至少在否定成本的情况下,Dijkstra会探索“刚刚偏离路径”的1和0的路径,而不是别的。在1路径下进行另一步的节点始终是队列中的最佳节点,它以目标节点结束,因此算法终止而不探索三角形的其余部分。它甚至看不到在左下角隐藏着一个99。考虑一下这个替代数据集:
01
00 01
00 00 01
00 00 00 01
99 00 00 00 01
至少在否定成本的情况下,Dijkstra会探索“刚刚偏离路径”的1和0的路径,而不是别的。在1路径下进行另一步的节点始终是队列中的最佳节点,它以目标节点结束,因此算法终止而不探索三角形的其余部分。它甚至看不到在左下角隐藏着一个99。您说过在构建路径时打印路径。为了完成并节省我们的时间,您可以包含失败运行的输出吗?旁注:如果您正在创建一个没有父类的类(除了隐式
对象
),并且希望repr
和str
表单相同,只需定义\uuuuu repr\uuuuu
并保留\uu str uuuu
未定义;在执行str
转换时,如果未定义\uuuuu str\uuu
,Python会自动返回到\uuuu repr\uuu
。我现在添加了问题的路径。谢谢你的旁注。你不能用Dijkstra来做这件事。两个旁注:使用而不是不断地对列表排序,你应该在使用它之后关闭文件,或者更好地,将它用作上下文管理器。关于算法,您使用x.value
作为键来决定下一步探索哪个节点,但这是路径中最后一个节点的值,而不是整个路径的值。您应该使用当前总计数作为排序标准(因为您使用的是节点
两者