汉诺塔与二进制

汉诺塔与二进制

  最近看C算法,在讲到分治的时候,果然又看到了分治必讲的汉诺塔问题,不过书里同时又讲到了汉诺塔与二进制的关系,我觉得很有意思。

  汉诺塔(Hanoi)问题想必大家都很熟悉,玩过这个游戏的也不在少数,N个盘的汉诺塔最少能用2N-1次移动完成,用递归的方法,很容易写出一个求最优移动方法的程序:

     
     
     
     
1  void Hanoi( int N,  int A,  int B,  int C)
2 {
3      if (N == 0)  return;
4     Hanoi(N – 1, A, C, B);
5     printf(“%d-->%d\n”, A, C);
6     Hanoi(N – 1, B, A, C);
7 }

  下面我们要研究的是每次移动的规律。可以看到,这个程序的递归调用是对称的,每次递归中有两个递归调用,且每次调用求解问题的规模是一样的,下面我们以N=3为例画出这个递归调用的递归树如下:

汉诺塔与二进制_第1张图片

  最右边一列方框就是程序的输出,如果我们按照圆盘的大小给圆盘加上编号,如最小的盘为1号,最大的盘为N号,那么最右面的那一列数字就是每次移动的圆盘,从上至下就是移动的顺序。在这个例子中就是先将1号盘从A柱移动到C柱,再把2号盘从A柱移动到B柱……

  观察这个序列1,2,1,3,1,2,1,可以发现1每隔一个数字就出现一次,那其它的数字呢,有什么规律?

  为了搞清这个问题,我们先看另一个问题,假设有一把可以折起来的尺子,我们想将他分成2N等分,怎么分呢?2等分自然是对折,4等分就是再对折,那么2N等分自然就是对折N次,在折痕处画一条线,尺子就被2N等分了。

  我们换一种方式,首先在尺子的1/2处画一条长为N的线,把尺子分为左边和右边,再分别在左边的1/2和右边的1/2处各画一条长为N-1的线,如此进行下去,每隔尺子的1/2i长度就画一条长为N-i+1的线,那么最后,每隔尺子的1/2N长度画一条长为1的线,画出来的结果如下图所示(N=5):

  这个问题同样可以使用递归来解决:

     
     
     
     
 1  void Draw( int N,  int l,  int r)
 2 {
 3      int m = (l + r) / 2;
 4      if (N > 0)
 5     {
 6         Draw(N – 1, l, m);
 7         printf(“%d\n”, N);
 8         Draw(N – 1, m, r);
 9     }
10 }

  注意到,这个递归调用也是对称的,而且每次递归的规模都与汉诺塔相同,那么他的递归调用是否跟汉诺塔一样呢?同样以N=3为例画出递归树:

汉诺塔与二进制_第2张图片

  我们发现,他们的递归树确实是一样的,连右边的序列都是一样的。这是因为他们的递归结构相同,每次递归的规摸也是一样,因此这两个问题的本质是一样的。尺子上的刻度的长度序列就是汉诺塔移动圆盘的序列!

汉诺塔与二进制_第3张图片

  那么这个序列到底有什么规律呢?尺子每次都是对折,也就是长度除以2,是否和2进制有关?以上面我们画出的那个N=3的尺子为例,我们把从1开始的二进制数写出来,看看有没有对应关系:       

        0001

        0010                   1

        0011

        0100                   2

        0101

        0110                   1

        0111

        1000                   3

        1001

        1010                  1

        1011

        1100                  2

        1101

        1110                  1

        1111

  

  发现了吗?这个序列的第i个数就等于2*i这个数的二进制末尾0的个数!

  如果还不太确定,可以把这个序列继续写下去,你会发现确实是这样的,而且这个序列中,1每隔1个数字就出现一次。想想我们在玩汉诺塔的时候,每一步其实只有两种选择:一是把最小的那个盘(因为他总在最上面)移动到别的杆上,二就是把某个杆上的第一个盘移动到另一个杆上(目标杆的盘必须比他大)。那么其实我们每隔一步就会移动一次最小的那个盘,这就对应了这个隔一个数字出现一次的1。

  这是为什么呢?我也不太清楚……

  总之,有了这个规律,我们就不用写递归程序了,只要把所有的偶数从小到大排开,计算他们末尾二进制的0的个数,我们就知道要移动哪个盘了。

 

  另外,这里是对于传统的汉诺塔,即3杆汉诺塔。那么4杆呢?5杆呢?N杆呢?对于N>3的情况,现在还没有一个确定的表达式可以计算出最少需要多少步,但是有一个递推公式。k杆汉诺塔问题即有n个圆盘,k个杆,圆盘初始在第一个杆上,要将圆盘移动到最后一个杆上。

  因为在最优解中,肯定会先将m个(1<=m<=n-1)个圆盘移动到某个中间杆上,再将剩下的n-m个圆盘通过剩下的k-1个杆移动到目标杆上,最后再将m个圆盘移动到目标杆上,因此有

  f(n,k)=min{2*f(m,k)+f(n-m,k-1)}, 1<=m<=n-1

你可能感兴趣的:(汉诺塔与二进制)