细解递归经典问题—汉诺塔

你好,我是goldsunC
让我们一起努力吧!

汉诺塔问题

公元1883年法国数学家Lucas所提出流传再印度的汉诺塔(Tower of Hanoil)游戏,是使用递归方法与堆栈概念来解决问题的典型范例。

汉诺塔游戏题目如下:

有三根木桩,第一根上面有n个盘子,最底层的盘子最大,最上层的盘子最小。汉诺塔问题就是将所有的盘子从第一根木桩开始,以第二根木桩为桥梁,全部搬到第三根木桩

在搬动盘子时需要遵守以下游戏规则:

  • 每次只能从某个木桩的最上面移动一个盘子。
  • 任何盘子可以从任何木桩搬到其它木桩。
  • 直径小的盘子永远只能放在直径比其大的盘子上面。

传说在远东的一个寺庙里,僧侣们需要将一摞按照由底向上由大到小排列的64个盘子从第一根木桩移到第三根木桩,当他们刚遇到这个问题的时候,陷入了困境,不知道如何解决这个问题。它们开始互相推卸责任,最后推到了寺庙住持的身上。

然后住持对副住持说:“你只要能把前63个盘子由第一个木桩移动到第二个木桩,我就可以完成第64个盘子的移动”。然后副住持对一个僧侣说:“你只要能把前62个盘子由第一个木桩移动到第二个木桩上,我就可以完成第63个盘子的移动”。…第63个僧侣对第64个僧侣说:“你只要能把前一个盘子移动到第二个木桩,我就能完成第2个盘子的移动”。第64个僧侣很快完成了它的操作,然后第63个僧侣也很快完成了他的操作,然后第62个、61个…

盘子比较多,一步步来可能不好理解,那么咱先从一个盘子说起。

  • 1个盘子:
    直接从1号木桩移动到3号木桩即可,共需要移动1次。
  • 2个盘子:
    • step1:从1号木桩移动到2号木桩。
    • step2:从1号木桩移动到3号木桩。
    • step3:从2号木桩移动到3号木桩。
      总共需要移动2^2-1=3次。
  • 3个盘子:
    • step1:从1号木桩移动到3号木桩。
    • step2:从1号木桩移动到2号木桩。
    • step3:从3号木桩移动到2号木桩。(这个时候把前n-1个由1号木桩移动到了2号木桩)
    • step4:从1号木桩移动到3号木桩
    • step5:从2号木桩移动到1号木桩。
    • step6:从2号木桩移动到3号木桩。
    • step4:从1号木桩移动到3号木桩。
      总共需要移动23-1=7次。

依次类推,我们能够计算出当由n个盘子时,总共需要移动的次数是2n-1次。假如是64个盘子,总共需要移动264-1 = 18446744073709551615 次,这个数有多大…,简单算了一下,一秒钟如果能移动1亿次的话,总共需要移动五千八百多年,那假如只允许一天移动一次…,果真是到了世界末日啊。

这个时候相信大家都能看出来这是一个典型的递归问题,满足递归的两大特性:

  • 有反复执行的过程。
  • 由停止执行的出口。

OK,知道是递归,那到底该怎么递归?既然是递归,那我们不妨就倒着推应该怎么移动。

首先,如果需要把n个盘子从第一根木桩移动到第三根,那么肯定要把第一根木桩的最后一个盘子先移动到第三根木桩,而最后一个盘子又被压在了第一根木桩的最下面,那么肯定首先要把前n-1个木桩从第一根木桩移动到第二根(中转木桩),然后再把第n个盘子从第一根木桩移动到第三根,最后把中转木桩上的n-1个盘子移动到第三根上。

哎,到这儿稍停,让我们退回一步,此时第n个盘子在第三根木桩上,前n-1个盘子在第二根木桩,试问我们能把这n-1个盘子直接从2号木桩移动到3号木桩吗?肯定是不可能的,我们还需要借助第一根和第三根,因为第三根上面是最大的那个盘子,因此我们可以简单理解为并没有盘子。

刚才我们把第n个盘子移动到了第三根木桩,这个时候需要把剩下的n-1个盘子移动到第三根木桩,那应该怎么办,对,和刚开始一样,需要把这n-1个盘子中的前n-2个移动到第一根木桩,然后把第n-1个移动到第三根,再把剩下的n-2个移动到第三根。

上面我们分别先把第n个和第n-1个移动到了第三根木桩上,移动这两个的时候有什么不同呢?

  • 移动第n个的时候,起点在第一根木桩,中转木桩是第二根,目标是第三根。
  • 移动第n-1个的时候,起点是第二根木桩,中转木桩是第一根,目标是第三根。

继续往下推导的话不难理解,每当把一个木桩移动到第三根木桩(目标木桩)上的时候,另外两个木桩会更改一下自身的定位,也就是起点变中转、中转变起点。这样改变是递归调用的结果。

仔细分析下来发现,我们并不需要知道每一步是怎么移动的,我们以刚才的1,2,3号木桩分别为起点木桩,中转木桩,目标木桩,假如倒推的话,我们发现这样操作就行:

  • 盘子n:将前n-1个盘子移动到中转木桩,将第n个盘子移动到目标木桩
  • 盘子n-1:将前n-2个盘子移动到起点木桩,将第n-1个盘子移动到目标木桩
  • 盘子n-2:将前n-3个盘子移动到中转木桩,将第n-2个盘子移动到目标木桩
  • 盘子n-3:将前n-4个盘子移动到起点木桩,将第n-3个盘子移动到目标木桩
  • 盘子n-(n-3):将前2个盘子从当时的起点木桩移动到当时的中转木桩,将第3个盘子移动到目标木桩
  • 盘子n-(n-2):将第1个盘子从当时的起点木桩移动到当时的中转木桩,将第2个盘子移动到目标木桩
  • 盘子n-(n-1):将第一个盘子移动到目标木桩

OK,正着推不理解,倒着推算是理解了,那每一步又应该是怎么移动的呢?注意一点,怎么移动的我们无需关注!交给程序就行了!接下来上代码,分别是Python、Java和C的:

n = 1
def hanoil(level,fromWhere,temp,toWhere):
    global n = 1
    if level == 1:
        print("Step",n,":","从",fromWhere,"移动到",toWhere)
    else:
        hanoil(level-1,fromWhere,toWhere,temp)
        hanoil(1,fromWhere,temp,toWhere)
        hanoil(level-1,temp,fromWhere,toWhere)
    n += 1
level = input('输入汉诺塔层数:')
hanoil(int(level),1,2,3)

这三种算法的原理一模一样,是不是乍一看不一样,哈哈哈别被骗了,下面这三句话作用一样,如果以Python的为例使用函数调用的话可能好理解一点,不过都一样啦,三种方式实现的效果也一模一样,感兴趣的自己运行下试试。

hanoil(1,fromWhere,temp,toWhere)
System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
printf("Step%d从%d移动到%d\n",n,from,to);

步骤如下:

Python:总共两情况4步

  • 如果是一层:
    • Step:从起点木桩到目标木桩。
  • 如果不是一层:
    • Step1:将前n-1个从起点木桩移动到中转木桩。
    • Step2:将剩下的一个直接移动到目标木桩。
    • Step3:将中转木桩的n-1个从中转木桩移动到目标木桩。

Java

public class HANOIL {
    
    
    public static void main(String[] args) throws IOException {
    
    
        int level;
        String str;
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入汉诺塔层数:");
        str = bf.readLine();
        level = Integer.parseInt(str);
        Hanoil(level,1,2,3);
    }
    private static int n = 1;
    public static void Hanoil (int level, int from, int temp, int to) {
    
    
        if (level == 1)
            System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
        else {
    
    
            Hanoil(level-1,from,to,temp);
            System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
            Hanoil(level-1,temp,from,to);
        }
    }
}
#include <stdio.h>
void Hanoi(int n,int from,int temp,int to);
int main(void)
{
    
    
    int n;
    printf("请输入汉诺塔层数:");
    scanf("%d",&n);
    Hanoi(n,1,2,3);
    return 0;
}
void Hanoi(int n,int from,int temp,int to)
{
    
    
    if (n == 1)
    {
    
    
        printf("Step%d从%d移动到%d\n",n,from,to);
    }
    else
    {
    
    
        Hanoi(n-1,from,to,temp);
        printf("Step%d从%d移动到%d\n",n,from,to);
        Hanoi(n-1,temp,from,to);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45634606/article/details/108623452