【问题标题】:Trying to implement recursive Tower of Hanoi algorithm with arrays尝试用数组实现递归的汉诺塔算法
【发布时间】:2014-12-20 15:34:43
【问题描述】:

尽管这里有很多关于这个问题的问题,但没有一个能帮助我解决这个问题。我了解递归是什么,我可以自己轻松地用 2^n-1 步解决河内塔,但我在用 Python 编写算法时遇到了麻烦。基本情况有效,但我似乎找不到将“将 n-1 个磁盘移动到辅助 peg,然后将最大磁盘移动到目标 peg”的方法转换为数组操作,我不明白为什么最后一个元素当我在递归调用中弹出它时,它没有从数组中删除。

这是节目:

peg_a = [1,0]
peg_b = []
peg_c = []

def hanoi(start,aux,target):
    print(start,aux,target)
    if len(start) == 1:
        target.append(start.pop())
        print(start,aux,target)
    else:
        hanoi(start[1:],target,aux)
        target.append(start.pop())
        print(start,aux,target)

hanoi(peg_a, peg_b, peg_c)

这是打印出来的:

[1, 0] [] []
[0] [] []
[] [] [0]
[1] [0] [0]

有什么帮助吗?

【问题讨论】:

    标签: python arrays algorithm python-3.x recursion


    【解决方案1】:

    我认为一个问题是你的函数没有返回任何东西。您使用列表来保存 bigs 的内容,它们是可修改的对象,因此您可以将函数参数视为指向这些对象的指针。这可能有效,但问题是通过使用start[1:] 进行切片,您会创建一个新列表,因此它不再是指向原始列表的“指针”。

    一种解决方法可能是仍然使用输入参数作为指向列表的指针,但添加一些额外的整数函数参数,这些参数指示要移动多少磁盘。

    我会这样做:

    def hanoi(pegs, start, target, n):
        assert len(pegs[start]) >= n, 'not enough disks on peg'
        if n == 1:
            pegs[target].append(pegs[start].pop())
            print '%i -> %i: %s' % (start, target, pegs)
        else:
            aux = 3 - start - target  # start + target + aux = 3
            hanoi(pegs, start, aux, n-1)
            hanoi(pegs, start, target, 1)
            hanoi(pegs, aux, target, n-1)
    

    我不使用 3 个不同的列表,因为在您的代码中它们会被交换,所以很难想象发生了什么。相反,我有一个 pegs 变量,它是一个列表列表。在我的例子中,starttarget 是我交换的钉子的索引。好消息是您现在可以打印各个步骤。快速演示:

    pegs = [[4, 3, 2, 1], [], []]
    hanoi(pegs, 0, 1, 4)    
    

    结果

    0 -> 2: [[4, 3, 2], [], [1]]
    0 -> 1: [[4, 3], [2], [1]]
    2 -> 1: [[4, 3], [2, 1], []]
    0 -> 2: [[4], [2, 1], [3]]
    1 -> 0: [[4, 1], [2], [3]]
    1 -> 2: [[4, 1], [], [3, 2]]
    0 -> 2: [[4], [], [3, 2, 1]]
    0 -> 1: [[], [4], [3, 2, 1]]
    2 -> 1: [[], [4, 1], [3, 2]]
    2 -> 0: [[2], [4, 1], [3]]
    1 -> 0: [[2, 1], [4], [3]]
    2 -> 1: [[2, 1], [4, 3], []]
    0 -> 2: [[2], [4, 3], [1]]
    0 -> 1: [[], [4, 3, 2], [1]]
    2 -> 1: [[], [4, 3, 2, 1], []]
    

    【讨论】:

    • 哦,有道理!我想这也解释了为什么某些元素在数组的最终状态中出现两次。是否可以通过在else 块中将start[1:] 分配给start 来修复它,或者这会导致该函数不操纵副本吗?
    • 它也缺少else 部分中的第二个递归调用。
    • 代码完美运行,将钉子作为嵌套列表是一个聪明的主意,我理解它的作用,但我发现很难想象 n 对每个调用的影响做。你能解释一下吗?
    • 参数n 就是您要移动的磁盘数量。递归技巧很简单:1)以任何方式将n-1磁盘移动到辅助挂钩2)移动底部的一个3)以任何方式将n-1个磁盘从辅助移动到目标。 “以任何方式”通过递归调用完成。该算法有效,因为您将大小为n 的问题分解为大小为n-1 的子问题,当您实际移动一个磁盘时,最终可以在n=1 的基本情况下解决。网上有many examples这个解释比较好。
    • 另见nice video
    【解决方案2】:

    你的代码有两个问题:

    • 虽然起初这似乎是一个聪明的主意,但使用 start[1:] 不起作用,因为您正在创建列表的副本,因此修改原始列表再列出来(参见 Bas 的回答)。
    • 您缺少else 部分中的第二个递归调用,将光盘从aux 挂钩堆叠到target 挂钩。

    要解决第一个问题,最简单的方法是添加一个附加参数,指示要从start 重定位到target 的磁盘数量:

    def hanoi(n, start, aux, target):
        if n == 1:
            target.append(start.pop())
        else:
            hanoi(n - 1, start, target, aux)
            target.append(start.pop())
            hanoi(n - 1, aux, start, target)
    

    或更短:

    def hanoi(n, start, aux, target):
        if n > 0:
            hanoi(n - 1, start, target, aux)
            target.append(start.pop())
            hanoi(n - 1, aux, start, target)
    

    【讨论】:

      猜你喜欢
      • 2017-06-30
      • 2012-09-11
      • 2019-09-21
      • 1970-01-01
      • 1970-01-01
      • 2017-01-31
      • 1970-01-01
      • 2011-10-20
      • 1970-01-01
      相关资源
      最近更新 更多