传智播客-递归(2)-汉诺塔及程序显示结果改进

汉诺塔是学习递归的经典问题。

 

有的问题可以用循环解决,也可以用递归,但是汉诺塔问题若采用循环方式来考虑,根本无从下手。汉诺塔问题即:有A,B,C三根柱子,A柱子上有N个盘子,由小至大依次叠放,要将这N个盘子通过辅助柱子B全部转移到C柱子上,转移的过程中依然遵循由小至大的叠放顺序。要求编写一段程序打印出每次移动的过程及总共的移动次数。

 

思考过程:
(个人以为,递归解决这样的问题,第一步主要是采用逆向思考的方式,有个传说中的问题,海盗分钻石的故事,据说是MS的智力考试题,解决思路就是采用的逆向思维,在这里,简单地说,就是倒推)

 

最后一步的现况,肯定是第N个盘子留在A柱子,其余的都在B柱子上,然后先将A柱子上的盘子移动到C,再把B上的盘子全部转移过来--这个最后一步,是单指从C柱子上叠放盘子顺序的最后一步(即只关注单个柱子叠放盘子的过程,没有考虑ABC三个柱子之间盘子转移的过程),这个是倒推。

 

然后采用递归的思考要点(1)将一个大问题分解为有限多个同质的小问题,但是暂不考虑小问题的具体处理细节和过程(详情请参见上一篇),这样的话,其实上面所讲的“最后一步”,就可以理解为“第N步”,因为每一个盘子由大至小依次转移到C柱子上的时候,过程都是一样的,或者说,同质的。那么,“第N步”,就又可以理解为“每一步”了,这样,第一个小问题就抽取出来了。

 

然后,对上面的“每一步”再细分,还是采用递归思考要点(1),先再分成有限多个小问题,不过这次要关注ABC之间盘子转移的过程了。
(1)将A上的第N个盘子放到C上,从这一步抽取的方法设为moveOne(int Nth, String to),Nth是指第N个盘子,to是指C。
(2)将B上的N-1个盘子放到C上,此时肯定以A为辅助柱子,这一步的方法可以设为moveAll(int total, String from, String mid, String to ),total是N-1,from是B,mid是A,to是C。

 

问题还没完。

 

需要再往上回溯一步。(3)A盘子上的N-1个盘子如何移到B上的呢?显然是以C为辅助转移过去的。这一步仍然采用moveAll方法,total是N-1,from是A,mid是C,to是B。(因为整个过程是以A为起点,C为终点,所以这一步是必须考虑的)

 

这样,将N个盘子从A借助B转移到C的过程就可以分解为上述三个步骤,顺序为(3)-(1)-(2)。而程序的起点是moveAll(int N, String “A”, String “B”, String to “C”),然后在这个程序中依次调用上述三个方法。

 

至于移动的总次数,只要统计moveOne的调用次数就可以了。因为实际的移动是由这个方法产生的。

 

移动的具体过程显示有三种方式:
(1)在moveOne方法里加一条打印语句。
(2)将ABC三个柱子的盘子数存放在三个相应的列表里,然后再将柱子和对应的列表存放到一个map里面。然后再写一个showAll方法打印出每个列表的数据。每调用一次moveOne,就更改一次ABC列表的数据,然后调用showAll方法。
(3)如下图所示:有三条并排的涌道,分别代表ABC三个柱子,对应(2)里面的三个列表。涌道用竖线“|”表示,盘子用减号“-”表示,为了美观,第N个盘子用2N个“-”表示。则涌道的高度为盘子数N,每个涌道的宽度为2N。

 

代码设计思路:
1、每调用一次showOne就打印一次三个涌道,还是showAll方法。
2、打印的顺序是先水平再垂直,则垂直方向的代码表示可以设为for(i<N),然后在这个for循环里打印每一行的结果。
3、水平方向,涌道的间隔“|”是固定的,四个,每两个中间打印相应的盘子数,有的话均(表现出)居中打印,没有则为空。
4、打印盘子的代码可以单独设立一个(咳咳。。用专业的说法,就是方法的封装),定为showLine(int theN),theN表示为该柱子(列表)中的第几个盘子。(个人以为这段图形显示代码的难点就在这个theN的处理上。)
5、从图中可以看出,每少一个盘子,涌道左右就各多一个空格(因为2个减号表示一个盘子,相应的,2个空格就表示缺失的一个盘子),因此,从左至右打印的顺序是,先打印N-theN个空格,再打印2theN个减号,再打印N-theN个空格。

hanoi3

 

下面是(2)的代码(下面的showAll方法为protected,是因为我写Hanoi3继承该类时复写了这个方法):
public class Hanoi2 {
 protected static int count = 0;
 protected Integer total = 0;
 protected List<Integer> aList = new ArrayList<Integer>();
 protected List<Integer> bList = new ArrayList<Integer>();
 protected List<Integer> cList = new ArrayList<Integer>();
 protected static HashMap<String, List<Integer>> map = new HashMap<String, List<Integer>>();
 
 public Hanoi2(){}
 
 public Hanoi2(Integer total){
  this.total = total;
  for(int i = 1; i <= total; i++){
   aList.add(i);   
  }
  map.put("A", aList);
  map.put("B", bList);
  map.put("C", cList);
 }
 
 public void startMove(){
  showAll();
  moveAll(total, "A", "B", "C");
 }
 
 protected void showAll(){
  System.out.println("A: " + map.get("A"));
  System.out.println("B: " + map.get("B"));
  System.out.println("C: " + map.get("C"));
 }

 private void moveAll(int total, String from, String mid, String to){
  
  if ( total == 1){
   moveOne(total, from, to);
  }else{
   moveAll(total - 1, from, to, mid);
   moveOne(total, from, to);
   moveAll(total - 1, mid, from, to);
  }
 }
 
 private void moveOne(int theN, String from, String to){
  map.get(to).add(0, map.get(from).remove(0));
  System.out.println("move the " + theN + "th from " + from + " to " + to);
  count++;
  showAll();
 }
 
 public int getCount(){
  return count;
 }
 
 public static void main(String[] args) {
  Hanoi2 hanoi = new Hanoi2(3);
  hanoi.startMove();
  System.out.println(hanoi.getCount());
 }
}

第(3)个方法,theN数值的处理算法为:
int n = 0;
if(i >= total - aList.size()) { //确认队列里有盘子了,或者说确认,到了队列里开始存放盘子的位置了
 n = (Integer)aList.get(i - (total - aList.size())); //从开始存放盘子的位置起重新计算取盘子的下标值
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值