汉诺塔

## 1、汉诺塔问题

汉诺塔问题是一个经典的问题,把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?
在这里插入图片描述
如果是初次接触类似的问题,乍看之下肯定会感觉无从下手。
要把64个圆盘从a柱子移动到c柱子上,第一步应该怎么做?虽然可以肯定,第一步唯一的选择是移动a最上面的那个圆盘,但是应该将其移到b还是c呢?很难确定。因为接下来的第二步、第三步……直到最后一步,看起来都是很难确定的。能立即确定的是最后一步:最后一步的盘子肯定也是a最上面那个圆盘,并且是由a或b移动到c——此前已经将63个圆盘移动到了c上。
也许你会说,管他呢,先随便试着移动一下好了。如果你这么做,你会发现,接下来你会面临越来越多类似的选择,对每一个选择都“试”一下的话,你会偏离正确的道路越来越远,直到你发现你接下来无法进行为止。

2、分解问题

一股脑地考虑每一步如何移动很困难,我们可以换个思路。先假设除最下面的盘子之外,我们已经成功地将上面的63个盘子移到了b柱,此时只要将最下面的盘子由a移动到c即可。如图:
在这里插入图片描述
当最大的盘子由a移到c后,b上是余下的63个盘子,a为空。因此现在的目标就变成了将这63个盘子由b移到c。这个问题和原来的问题完全一样,只是由a柱换为了b柱,规模由64变为了63。因此可以采用相同的方法,先将上面的62个盘子由b移到a,再将最下面的盘子移到c……对照下面的过程,试着是否能找到规律:

  1. 将b柱子作为辅助,把a上的63个圆盘移动到b上
  2. 将a上最后一个圆盘移动到c
  3. 将a作为辅助,把b上的62个圆盘移动到a上
  4. 将b上的最后一个圆盘移动到c

也许你已经发现规律了,即每次都是先将其他圆盘移动到辅助柱子上,并将最底下的圆盘移到c柱子上,然后再把原先的柱子作为辅助柱子,并重复此过程。
这个过程称为递归,即定义一组基本操作,这组操作将规模小一点(或大一点)的操作当做一个整体——无需关心它的细节,只当它已经完成了——然后执行剩下的操作。而在更小或更大的规模中也依此操作,直到规模达到预定值。

递归实际上就是用自己来定义自己。
回到前面汉诺塔的问题上来。我们假设函数func(n, a, b, c)用于将n个圆盘由a移动到c,b作为辅助柱子。那么我们可以这样实现这个递归过程:

func:if n!=0 then       ;预定值
  func(n-1, a, c, b)    ;将n-1个盘子由a移动到b,以c为辅助柱子(注意参数顺序)
  move a[n] to c        ;将a上的最后一个盘子移动到c
  func(n-1, b, a, c)    ;将n-1个盘子由b移动到c,以a为辅助柱子
endif                   ;完成

3、程序实现

resolve方法用来移动盘子,参数n表示要移动的盘子的数量,a是盘子所在的柱子,b是辅助柱子,c是目标柱子。

import java.util.Iterator;
import java.util.Stack;

public class HanoiTower {
      public static void print(Stack<Integer> s) {
            Iterator<Integer> i = s.iterator();
             while (i.hasNext()) {
                  System.out.printf("%d ", i.next());
            }
            System.out.println();
      }

      public static void resolve(int n, Stack<Integer> a, Stack<Integer> b, Stack<Integer> c) {
            if (n==0) return;
            resolve(n-1, a, c, b);
            c.push(a.pop());
            resolve(n-1, b, a, c);
      }

      public static void main(String[] args) {
            int count = 16;
            Stack<Integer> a = new Stack<Integer>();
            Stack<Integer> b = new Stack<Integer>();
            Stack<Integer> c = new Stack<Integer>();

            for (int i=count; i>0; i--) {
                 a.push(i);
            }

            print(a);
            long start = System.currentTimeMillis();
            resolve(count, a, b, c);
            long end = System.currentTimeMillis();
            print(c);

            System.out.println((end - start)/1000);
      }
}

码云地址

汉诺塔问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值