Project Euler问题#18 Python-得到错误的结果。为什么?

Project Euler问题#18 Python-得到错误的结果。为什么?,python,algorithm,Python,Algorithm,在下班后的最后几天里,我试图解决Euler项目,作为学习Python的练习,现在我在 我研究了这个问题,认为可以使用Dijkstra算法解决,将节点的值作为负整数,从而找到“最长”路径 我的解决方案似乎几乎是正确的(我得到1068),也就是说是错的。它打印了一条路径,但据我所知,它不是正确的路径。但从一段时间以来我一直在看,我不知道为什么 也许这个问题不能用我的方法解决,我需要一些其他的方法,比如动态规划——或者也许我对Dijkstra的实现是错误的 我很有信心从文件到图形的解析是按预期进行的

在下班后的最后几天里,我试图解决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
作为键来决定下一步探索哪个节点,但这是路径中最后一个节点的值,而不是整个路径的值。您应该使用当前总计数作为排序标准(因为您使用的是
节点
两者