河内塔:递归算法

时间:2009-08-03 16:33:34

标签: recursion towers-of-hanoi

虽然我对理解递归没有任何问题,但我似乎无法绕过河内塔问题的递归解决方案。以下是Wikipedia的代码:

procedure Hanoi(n: integer; source, dest, by: char);
Begin
    if (n=1) then
        writeln('Move the plate from ', source, ' to ', dest)
    else begin
        Hanoi(n-1, source, by, dest);
        writeln('Move the plate from ', source, ' to ', dest);
        Hanoi(n-1, by, dest, source);
    end;
End;

我理解基本情况以及将问题分解成小块的概念,直到您能够移动单个磁盘。但是,我无法弄清楚非基本情况下的两个递归调用是如何协同工作的。也许有人可以帮助我?感谢。

28 个答案:

答案 0 :(得分:46)

实际上,section from where you took that code也提供了解释:

  

将n个光盘从弦轴A移动到弦轴C:

     
      
  1. 将n-1个光盘从A移动到B.这样就可以在光盘A上单独留下光盘#n
  2.   
  3. 将光盘#n从A移动到C
  4.   
  5. 将n-1张光盘从B移动到C,因此它们位于光盘#n
  6. 上   

很明显,您首先必须删除 n - 1张光盘才能访问 n 。并且你必须先将它们移动到另一个钉子上,而不是你希望整个塔子出现的位置。

除了光盘数量之外,帖子中的代码还有三个参数: source peg, destination peg和 temporary peg哪些光盘可以存储在哪些光盘之间(每个尺寸 n -1的光盘都适合)。

递归实际上发生了两次,一次发生在writeln之前,一次发生之后。 writeln之前的那个将 n - 1个光盘移动到临时挂钩上,使用目标挂钩作为临时存储(递归调用中的参数的顺序不同)。之后,剩余的光盘将被移动到目标钉,然后第二次递归通过将 n - 1塔从临时栓移动到目标栓,从而强制移动整个塔。光盘 n。

答案 1 :(得分:31)

一年前,我有一个函数式编程课程,并为该算法绘制了这个插图。 希望它有所帮助!

(0)  _|_         |          |
    __|__        |          |
   ___|___       |          |
  ____|____  ____|____  ____|____

(1.1) |          |          |
    __|__        |          |
   ___|___      _|_         |
  ____|____  ____|____  ____|____ (A -> B)

(1.2) |          |          |
      |          |          |
   ___|___      _|_       __|__
  ____|____  ____|____  ____|____ (A -> C)

(1.3) |          |          |
      |          |         _|_
   ___|___       |        __|__
  ____|____  ____|____  ____|____ (B -> C)



(2.1) |          |          |
      |          |         _|_
      |       ___|___     __|__
  ____|____  ____|____  ____|____ (A -> B)



(3.1) |          |          |
      |          |          |
     _|_      ___|___     __|__
  ____|____  ____|____  ____|____ (C -> A)

(3.2) |          |          |
      |        __|__        |
     _|_      ___|___       |
  ____|____  ____|____  ____|____ (C -> B)

(3.3) |         _|_         |
      |        __|__        |
      |       ___|___       |
  ____|____  ____|____  ____|____ (A -> B)

3环问题已被分解为2个2环问题(1.x和3.x)

答案 2 :(得分:14)

http://www.cs.cmu.edu/~cburch/survey/recurse/hanoiimpl.html的递归河内实施有一个很好的解释。

总结一下,如果你想将底板从杆A移动到杆B,你首先必须将它上面的所有较小的盘子从A移动到C.第二个递归调用是移动盘子你在您的基础案例将单个大板从A移动到B之后,将C移回到B.

答案 3 :(得分:13)

我同意,当你第一次看到它时,这个并不是立竿见影的,但是当你开始研究它时,它是相当简单的。

基础案例:你的塔的大小为1.所以你可以一步到位,从源头到目的地。

递归案例:你的塔的大小为n> 1.所以你将尺寸为n-1的顶部塔架移动到一个额外的钉子(by),将尺寸为1的底部“塔”移动到目标钉上,然后将顶部塔从一端移动到目的地。

因此,对于一个简单的案例,你有一个高度为2的塔:

 _|_    |     |
__|__   |     |
===== ===== =====

第一步:将2-1(= 1)的顶部塔移动到额外的钉子(中间的那个,让我们说)。

  |     |     |
__|__  _|_    |
===== ===== =====

下一步:将底部光盘移动到目的地:

  |     |     |
  |    _|_  __|__
===== ===== =====

最后,将(2-1)= 1的顶部塔移动到目的地。

  |     |    _|_
  |     |   __|__
===== ===== =====

如果你考虑一下,即使塔是3或更多,总会有一个空的额外挂钉,或者所有较大圆盘的挂钉,用于在交换塔时使用的递归。

答案 4 :(得分:4)

假设我们想要通过B将光盘从A移动到C:

  1. 将较小的光盘移至B.
  2. 将另一张光盘移至C。
  3. 将B移至C.
  4. 从A移至B.
  5. 将所有人从C移至A.
  6. 如果您重复上述所有步骤,光盘将会传输。

答案 5 :(得分:2)

在阅读了所有这些解释后,我认为我会用我教授用来解释河内塔递归解决方案的方法来衡量。这是算法, n 表示环数,A,B,C表示挂钩。该函数的第一个参数是环数,第二个参数表示源挂钩,第三个参数表示目标挂钩,第四个参数是备用挂钩。

procedure Hanoi(n, A, B, C);
  if n == 1
    move ring n from peg A to peg B
  else
    Hanoi(n-1, A, C, B);
    move ring n-1 from A to C
    Hanoi(n-1, C, B, A);
end;

我在研究生院接受过教育,从不羞于思考小事。那么,让我们看一下这个算法的n = 5.首先要问自己的问题是如果我想将第5个环从A移动到B,其他4个环在哪里?如果第5个环占用钉子A,我们想将它移动到钉子B,然后其他4个环只能在钉子C上。在上面的算法中,函数河内(n-1,A,C,B)试图将所有其他4个环移动到栓C上,因此环5将能够从A移动到B.遵循此算法我们看n = 4.如果环4将从A移动到C,其中是戒指3和更小?它们只能在钉子B上。接下来,对于n = 3,如果环3将从A移动到B,那么环2和1在哪里?当然是挂钩C。如果继续遵循此模式,则可以看到递归算法正在执行的操作。这种方法与新手的方法不同,它首先查看最后一个磁盘,最后查看第一个磁盘。

答案 6 :(得分:2)

将其视为堆栈,磁盘直径由整数表示(4,3,2,1) 第一次递归调用将被调用3次,从而填充运行时堆栈,如下所示

  1. 第一个电话:1。第二个电话:2,1。和第三个电话:3,2,1。
  2. 在第一次递归结束后,运行时堆栈的内容从最大直径弹出到中间极点(最后一个出口)。接下来,将直径为4的磁盘移动到目标位置。

    第二次递归调用与第一次递归调用相同,除了将元素从中间极移动到目标。

答案 7 :(得分:2)

我感到痛苦!

虽然这是一篇很老的帖子,但我认为真正需要理解的是,不是“移动到那个”方法,而是答案涉及使用递归的副作用。

对我来说非常宝贵的帮助是“The Little Schemer”,它教会人们思考和编写递归函数。

然而,这教导读者在下一次递归调用中使用返回结果的结果。

在河内塔中,答案不在于返回的结果本身,而在于观察返回的结果。

magic 出现在函数参数的连续重新排列中。

是的,问题实际上分为三个部分:

  • 将较小的塔移动到备用桩上
  • 将最后一张光盘移至目标钉
  • 将备用挂钩上的剩余塔移动到目标挂钩。

在Scheme中:

(define (th n a b c)
    (if (zero? n) 'done
        (begin
           (th (- n 1) a c b)
           (display (list a c))
           (newline)
           (th (- n 1) b a c))))
(th 5 'source 'spare 'destination)

然而,显示函数参数是问题的解决方案,并且至关重要地理解调用的双树结构。

该解决方案还向所有与传统控制结构进行过搏斗的程序员传达proof by induction暖光的力量。

很明显,手动解决问题非常令人满意。

  • 计算光盘数量
  • 如果均匀,将第一张光盘移动到备用挂钩,进行下一次合法移动(不涉及顶部光盘)。然后将顶部光盘移动到目标挂钩,进行下一次合法移动(nittd)。然后将顶部光盘移动到源钉,进行下一次合法移动(nittd)......
  • 如果奇怪,将第一张光盘移动到目的地挂钩,进行下一次合法移动(不涉及顶部光盘)。然后将顶部光盘移动到备用挂钩,进行下一次合法移动(nittd)。然后将顶部光盘移动到源钉,进行下一次合法移动(nittd)......

最好通过始终用同一只手握住顶部光盘并始终将手向同一方向移动来完成。

n光盘的最终移动次数为2^n - 1 move n disc to destination已完成整个过程。

最后,如何向同事,你的妻子/丈夫甚至是狗(即使他们不听)解释问题也很有趣。

答案 8 :(得分:1)

有三座塔,即源塔,目的塔和辅助塔。源塔具有所有磁盘,您的目标是将所有磁盘移动到目标塔,并确保在执行此操作时,您永远不会将较大的磁盘放在较小的磁盘上。我们可以使用以下步骤中的递归来解决这个问题:

我们在源塔上有 n 个磁盘数量

基本情况:n = 1   如果源塔中只有一个磁盘,请将其移至目标塔。

递归案例:n> 1

  • 将顶部n-1个磁盘从源塔移动到辅助塔
  • 将唯一剩余的第n个磁盘(在第1步之后)移动到目标位置 塔
  • 现在将辅助塔中的n-1个磁盘移动到目标
    塔,使用源塔作为帮手。

Java中的源代码:

private void towersOfHanoi(int n, char source, char destination, char helper) {
    //Base case, If there is only one disk move it direct from source to destination
    if(n==1){
        System.out.println("Move from "+source+" to "+destination);
    }
    else{
        //Step1: Move the top n-1 disks from source to helper
        towersOfHanoi(n-1, source, helper, destination);
        //Step2: Move the nth disk from source to destination
        System.out.println("Move from "+source+" to "+destination);
        /*Step3: Move the n-1 disks(those you moved from source to helper in step1) 
         * from helper to destination, using source(empty after step2) as helper
         */
        towersOfHanoi(n-1, helper, destination, source);
    }
}

答案 9 :(得分:1)

public static void hanoi(int number, String source, String aux, String dest)
{
    if (number == 1)
    {
        System.out.println(source + " - > "+dest);
    }
    else{
        hanoi(number -1, source, dest, aux);
        hanoi(1, source, aux, dest);
        hanoi(number -1, aux, source, dest);
    }
}

答案 10 :(得分:1)

正如我们的一些朋友建议的那样,我删除了之前的两个答案,并在此处进行了整合。

这让你清楚地了解。

通用算法是什么......

算法:

solve(n,s,i,d) //solve n discs from s to d, s-source i-intermediate d-destination
{
    if(n==0)return;
    solve(n-1,s,d,i); // solve n-1 discs from s to i Note:recursive call, not just move
    move from s to d; // after moving n-1 discs from s to d, a left disc in s is moved to d
    solve(n-1,i,s,d); // we have left n-1 disc in 'i', so bringing it to from i to d (recursive call)
}

这是工作示例Click here

答案 11 :(得分:1)

问题的答案,程序如何知道,甚至是“src”到“aux”,而奇数是“src”到“dst”的开放动作在于程序。如果你用4张光盘打破拳头移动,那么这看起来像这样:

hanoi(4, "src", "aux", "dst");
if (disc > 0) {
    hanoi(3, 'src', 'dst', 'aux');
        if (disc > 0) {
            hanoi(2, 'src', 'aux', 'dst');
                if (disc > 0) {
                    hanoi(1, 'src', 'dst', 'aux');
                        if (disc > 0) {
                            hanoi(0, 'src', 'aux', 'dst');
                                END
                        document.writeln("Move disc" + 1 + "from" + Src + "to" + Aux);
                        hanoi(0, 'aux', 'src', 'dst');
                                END
                        }

也是第一步有4碟(偶数)从Src到Aux。

答案 12 :(得分:1)

这很简单。假设您要从A移动到C

如果只有一个磁盘,只需移动它。

如果有多个磁盘,请执行

  • 移动所有磁盘(n-1个磁盘),底部除了A到B
  • 将底部磁盘从A移动到C
  • 将n-1个磁盘从第一步移到A到C

请记住,在移动n-1个磁盘时,第n个根本不会成为问题(一旦它比其他所有磁盘都大)

请注意,移动n-1个磁盘会再次出现同样的问题,直到n-1 = 1,在这种情况下,您将位于第一个if(您应该移动它的位置)。

答案 13 :(得分:1)

第一个递归调用通过使用dest作为辅助堆来移动除源中最大的一个之外的所有部分。完成后,除了最大的部分之外的所有部分都会依赖,最大的部分是免费的。现在你可以将最大的一个移动到dest并使用另一个递归调用将所有碎片移动到dest。

递归调用不会知道关于最大部分的任何信息(即他们会忽略它),但这没关系,因为递归调用只会处理较小的部分,因此可以移动到最大的部分上自如。

答案 14 :(得分:0)

这是递归调用河内塔的C ++代码。

#include <iostream>
void toh(int n, char A, char B, char C) {
    if(n == 1) {
        std::cout << "Move Disc 1 from " << A << " to " << C << std::endl;
        return;
    }
    toh(n-1, A, C, B);
    std::cout << "Move Disc " << n << " from " << A << " to " << C <<std::endl;
    toh(n-1, B, A, C);
}
int main() {
    int numberOfDisc;
    char A = 'A', B = 'B', C = 'C';
    std::cout << "Enter the number of disc: ";
    std::cin >> numberOfDisc;
    toh(numberOfDisc,  A, B, C);
    return 0;
}

答案 15 :(得分:0)

我强烈推荐这个 video,它以一种非常容易理解的方式说明了这个问题的递归。

关键是要了解解决方案中的重复模式。该问题可以分为三个主要的子问题。假设您有 n 个圆盘,它们被放置在 A 棒上。目标棒是 C,您有 B 作为中间棒。

主要问题;使用杆 B 将 n 个圆盘从杆 A 移动到杆 C。

  1. 使用杆 C 将 n-1 个圆盘从杆 A 移动到杆 B。
  2. 将最后一个圆盘从杆 A 移到杆 C。
  3. 使用杆 A 将 n-1 个圆盘从杆 B 移动到杆 C。

子问题 1 和 3 实际上是在您在上面看到的同一问题划分中解决的。 让我们来解决子问题 1。

子问题1;使用杆 C 将 n-1 个圆盘从杆 A 移动到杆 B。

  1. 使用杆 B 将 n-2 个圆盘从杆 A 移动到杆 C。
  2. 将最后一个圆盘从杆 A 移到杆 B。
  3. 使用杆 A 将 n-2 个圆盘从杆 C 移动到杆 B。

我们像解决主问题一样解决子问题。我们的基本情况是当磁盘的数量等于 1 时,我们将它从一根杆移动到目标一根,就是这样。

写下这些步骤会有很大帮助。

答案 16 :(得分:0)

我对代码here做了很小的更改,如果您查看输出,您会看到小零件图案在父节点中重复(将其视为分形图像)

def moveTower(height,fromPole, toPole, withPole, ar = ''):
    if height >= 1:
        print( "    "*(3-height), "moveTower:", height, fromPole, toPole, ar )
        moveTower(height-1,fromPole,withPole,toPole)
        moveDisk(fromPole,toPole,height)
        moveTower(height-1,withPole,toPole,fromPole, '*')
    #print(withPole)

def moveDisk(fp,tp,height):
    print("    "*(4-height), "moving disk", "~"*(height), "from",fp,"to",tp)


moveTower(3,"A","B","C")

输出为:

 moveTower: 3 A B 
     moveTower: 2 A C 
         moveTower: 1 A B 
             moving disk ~ from A to B
         moving disk ~~ from A to C
         moveTower: 1 B C *
             moving disk ~ from B to C
     moving disk ~~~ from A to B
     moveTower: 2 C B *
         moveTower: 1 C A 
             moving disk ~ from C to A
         moving disk ~~ from C to B
         moveTower: 1 A B *
             moving disk ~ from A to B

答案 17 :(得分:0)

今天刚刚看了这段视频:Recursion 'Super Power' (in Python) - Computerphile,我想我们绝对应该在这里有 Thorsten Altenkirch教授的代码,因为它是一段非常优美而优雅的递归代码,而且并非总是如此我们会有高质量的视频来显示答案。

def move(f,t) : 
    print("move disc from {} to {}!".format(f,t))

def hanoi(n,f,h,t) : 
    if n==0 : 
        pass
    else :
        hanoi(n-1,f,t,h)
        move(f,t)
        hanoi(n-1,h,f,t)

我们的hanoi函数具有4个参数:

  • n:光盘数量
  • f:光盘(来自)
  • 的来源
  • h:中间步骤“通过” (帮助程序)
  • t:我们希望光盘位于最后(目标)
  • 的最终位置
>>> hanoi(4,"A","B","C")
move disc from A to B!
move disc from A to C!
move disc from B to C!
move disc from A to B!
move disc from C to A!
move disc from C to B!
move disc from A to B!
move disc from A to C!
move disc from B to C!
move disc from B to A!
move disc from C to A!
move disc from B to C!
move disc from A to B!
move disc from A to C!
move disc from B to C!

enter image description here

答案 18 :(得分:0)

此python3示例使用递归解决方案:

# Hanoi towers puzzle
# for each n, you have to move n-1 disks off the n disk onto another peg
# then you move the n disk to a free peg
# then you move the n-1 disks on the other peg back onto the n disk

def hanoi(n):
    if n == 1:
        return 1
    else:
        return hanoi(n-1) + 1 + hanoi(n-1)


for i in range(1, 11):
    print(f"n={i}, moves={hanoi(i)}")

输出:

n=1, moves=1
n=2, moves=3
n=3, moves=7
n=4, moves=15
n=5, moves=31
n=6, moves=63
n=7, moves=127
n=8, moves=255
n=9, moves=511
n=10, moves=1023

但是,当然,计算出多少步的最有效方法是认识到答案总是小于2 ^ n 1。所以数学解是2 ^ n-1

答案 19 :(得分:0)

这是我使用golang进行递归的河内塔问题的解决方案代码。 `package main

import "fmt"

func main() {
    toi(4, "src", "dest", "swap")
}

func toi(n int, from, to, swap string) {
    if n == 0 {
        return
    }
    if n == 1 {
        fmt.Printf("mov %v %v -> %v\n", n, from, to)
        return
    }
    toi(n-1, from, swap, to)
    fmt.Printf("mov %v %v -> %v\n", n, from, to)
    toi(n-1, swap, to, from)
}`

答案 20 :(得分:0)

实际上我在 https://helloml.org/tower-of-hanoi-recursion/ 找到了一个很好的解释。我建议你去看看。

在这篇文章中,他们提到了解决这个问题的步骤。

<块引用>

解决河内塔问题的步骤:

  1. 将“n-1”个圆盘从源棒递归移动到辅助棒。
  2. 将第 n 个圆盘从源棒移动到目标棒。
  3. 将“n-1”个圆盘从辅助杆递归移动到目标杆。

考虑三个磁盘的情况,并用一组图像解释算法。

答案 21 :(得分:0)

这里是解释。看图片 - &gt;

enter image description here

通过拨打Movetower(3,a,b,c),您打算将所有3张光盘从塔A移至塔B。所以顺序调用是 - &gt;

1. Movetower(3,a,b,c)  // No Move needed
2. Movetower(2,a,c,b)  // No move needed
3. Movetower(1,a,b,c)  // Here is the time to move, move disc1 from a to b
4. Movetower(2,a,c,b)  // Returning to this call again, this is the time to move disc2 from a to c
5. Movetower(1,b,c,a)  // Again the time to move, this time disc1 from b to c
6. Movetower(3,a,b,c)  // Returning to this call again, this is the time to move disc3 from a to b
7. Movetower(2,c,b,a)  // Not the time to move
8. Movetower(1,c,a,b)  // Here is the time to move, move disc1 from c to a
9. Movetower(2,c,b,a)  // Returning to this call again, this is the time to move disc2 from c to b
10.Movetower(1,c,a,b)  // Here is the time to move, move disc1 from a to b

希望有所帮助:)

对于动画:https://www.cs.cmu.edu/~cburch/survey/recurse/hanoiex.html

答案 22 :(得分:0)

简单来说,我们的想法是在三个定义的塔中填充另一个塔,其顺序与现有光盘相同,但在此过程中的任何时候都没有较大的圆盘与小圆盘重叠。

让&#39; A&#39; ,&#39; B&#39;和&#39; C&#39;是三座塔楼。 &#39; A&#39;将是包含&#39; n&#39;光盘最初。 &#39; B&#39;可用作中间塔和&#39; C&#39;是目标塔。

算法如下:

  1. 从塔上移动n-1张光盘&#39; A&#39;到&#39; B&#39;使用&#39; C&#39;
  2. 从A&#39; A&#39;移动光盘到&#39; C&#39;
  3. 从塔上移动n-1张光盘&#39; B&#39;到&#39; C&#39;使用&#39; A&#39;
  4. java:

    中的代码如下 公共课TowerOfHanoi {

    public void TOH(int n, int A , int B , int C){
        if (n>0){
            TOH(n-1,A,C,B);
            System.out.println("Move a disk from tower "+A +" to tower " + C);
            TOH(n-1,B,A,C);
        }
    }
    
    public static void main(String[] args) {
        new TowerOfHanoi().TOH(3, 1, 2, 3);
    }   
    

    }

答案 23 :(得分:0)

作为一名CS学生,您可能听说过数学归纳。 河内塔的递归解决方案类似地工作 - 只有不同的部分才能真正得到B和C的损失,因为完整的塔结束了。

答案 24 :(得分:-1)

def Hanoi(n, A, B, C):
    if(n==1): print "move plates to empty peg"
    else:
       Hanoi(n-1, A, B, C)
       print "move"+str(n)+" to peg "+C
       Hanoi(n-1, B, C, A)

答案 25 :(得分:-1)

/ **  *  * / package com.test.recursion;

/ **  * @author kamals1986河内塔问题的递归算法  *算法通过幂(2,n)增长。  * / 公共课TowerOfHanoi {

private static String SOURCE_PEG = "B";

private static String SPARE_PEG = "C";

private static String TARGET_PEG = "A";

public void listSteps(int n, String source, String target, String spare) {
    if (n == 1) {
        System.out.println("Please move from Peg " + source + "\tTo Peg\t"
                + target);
    } else {
        listSteps(n - 1, source, spare, target);
        listSteps(1, source, target, spare);
        listSteps(n - 1, spare, target, source);
    }
}

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    new TowerOfHanoi().listSteps(18, SOURCE_PEG, TARGET_PEG, SPARE_PEG);

    long endTime = System.currentTimeMillis();

    System.out.println("Done in " + (endTime - startTime) / 1000
            + "\t seconds");
}

}

答案 26 :(得分:-1)

Tower (N,source,aux.dest):

  1. if N =1 Then
       Write : Source -> dest
       return
    end of if
    
  2. 将N-1磁盘从挂钩源移动到peg aux

    call Tower (N-1, source, dest, aux)
    
  3. 写源 - &gt; DEST
  4. 将N-1个磁盘从peg aux移动到peg dest

    call Tower (N-1, source, dest, aux)
    
  5. 返回

答案 27 :(得分:-2)

我也试图获得递归。

我发现了一种我认为的方式,

我认为它就像一连串的步骤(步骤不是常数,它可能会根据前一个节点而改变)

我必须找出两件事:

  1. 上一个节点
  2. step kind
  3. 在步骤之后调用之前的其他内容(这是下一次调用的参数
  4. 例如

    阶乘

    1,2,6,24,120 .........或

    1,2 *(1),3 *(2 * 1),4 *(3 * 2 * * 1.5(4 * 3 * 2 * 1)

    step = last by last node

    在我需要到达下一个节点的步骤之后,抽象1

    确定

    function =
    
    n*f(n-1) 
    
    its 2 steps process
    from a-->to step--->b
    

    我希望这有帮助,只考虑2个thniks,而不是如何从节点到节点,但节点 - &gt;步骤 - &gt;节点

    node - &gt; step是函数体 step - &gt; node是另一个函数的参数

    再见:)希望我帮助