zoukankan      html  css  js  c++  java
  • 四柱加强版汉诺塔HanoiTower----是甜蜜还是烦恼

    我想很多人第一次学习递归的时候,老师或者书本上可能会举汉诺塔的例子。

    但是今天,我们讨论的重点不是简单的汉诺塔算法,而是三柱汉诺塔的延伸。先来看看经典的三柱汉诺塔。

    一、三柱汉诺塔(Hanoi_Three):

    我想大家对于三柱汉诺塔的理解以及算法的实现应该是很熟练了。

    我在这里简单的过一遍三柱汉诺塔的算法思想:

    有A、B、C三根柱子,A柱上有n个盘子,现在需要将A上所有的盘子转移到C上,请给出搬运次数最少的步骤。

    算法思想:

    1、将A上n-1个盘子以C为缓存,全部转移到 B 柱上。

    2、将A上留下的第n个盘子,直接转移到 C  柱上。

    3、将B上的n-1个盘子,以A为缓存,全部转移到 C 柱上。

    很容易得到算法的递归方程为:T(n)=2*T(n-1)+1,不难算出步数是T(n)=2^n-1。

     

    具体的代码如下:

    [cpp] view plaincopy
     
     
    1. void Move( char x, char y )  
    2. {  
    3.     printf("%c --> %c ", x, y); //打印路径,x-->y  
    4. }  
    5.   
    6. void Hanoi_Three( int n, char a, char b, char c )  
    7. {  
    8.     if( n <= 0 )  
    9.         return ;      
    10.       
    11.     step++;     //步骤+1  
    12.       
    13.     if( n == 1 )  
    14.     {  
    15.         Move( a, c );   //将a柱上的一个盘子直接移动到c柱  
    16.         return ;      
    17.     }  
    18.     else  
    19.     {  
    20.         Hanoi_Three( n-1, a, c, b);     //将a上n-1个盘子以c为缓存,全部转移到 b 柱上  
    21.         Move( a, c );                   //将A上留下的第n个盘子,直接转移到 C  柱上  
    22.         Hanoi_Three( n-1, b, a, c);     //将b上的n-1个盘子,以a为缓存,全部转移到 c 柱上  
    23.     }  
    24. }  


    二、四柱汉诺塔(Hanoi_Four):

    当柱子为四根时,对于只是将盘子全部转移到另一根柱子上这个目的来说,是大大降低了难度,而且算法的复杂度也大大降低了。但是,这个时候,如果要你找到一个最优的、步骤最少的实现方法,可以说难度是提升了一个数量级。

    有些人可能会质疑,为什么,我用三柱汉诺塔的思想不是很优化了吗?

    别急,且让我慢慢向你道来。

    先来看看这种‘看上去很合理’的解法:

    假设,A,B,C,D,分别为:源位置,缓存,缓存,目的位置。

    因为三柱的时候,我们是将A的前n-1个盘子放到B上缓存,然后将第n个盘子放到C柱上。

    现在的情况好很多,有两个可以缓存的柱子,因此,看上去移动起来更加方便,原来B上需要缓存的n-1个盘子,现在可以只是n-2个盘子,而将第n-1个盘子放到C上缓存。

    具体的流程如下(非最优解法):

    1、从A借助C、D将 n-2个盘子移动到B上。

    2、将第n-1个盘子移动到C上。

    3、将第n个盘子移动到D上。

    4、将第n-1个盘子移动到D上。

    5、从B借助A、C将 n-2个盘子全部移动到D上。

    看上去,非常完美,笔者也一度觉得这个思想没有破绽,甚至我还自以为找到了k根柱子汉诺塔的通用方法(想当然的将B柱上缓存的数量从n-2个,改为n-(k-2)个盘子)。直到我看了这篇文章:多柱汉诺塔最优算法设计探究

        虽然我们想到让盘子尽量不发生重叠来保证步数的最少,但是这并不能绝对保证。或许在盘子较少的情况下是可行的,但是盘子增多时,那些多余的只有一个盘子的柱子是可以加以利用的(可能的优化在这里)。虽然这么做加多了每次的移动步数,但是却从另一个侧面减少了递归的数量,因此我们需要从这里边找一个平衡点。

    下面我们来看看,1941年,美国的J. S. Frame,给出的四柱汉诺塔的算法思想,也叫Frame算法

    1、用4柱汉诺塔算法把A柱上部分的n- r个碟子通过C柱和D柱移到B柱上【F( n- r )步】。

    2、用3柱汉诺塔经典算法把A柱上剩余的r个碟子通过C柱移到D柱上【2^r-1步】(参照上述三柱时的情况)。

    3、用4柱汉诺塔算法把B柱上的n-r个碟子通过A柱和C柱移到D柱上【F(n-r)步】。

    4、依据上边规则求出所有r(1≤r≤n)情况下步数f(n),取最小值得最终解。

     

    因此Frame算法的递归方程如下:

    F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n)。

     

    大家有没有发现,其实,这个算法思想跟我们之前认为合理的算法基本一致,差别只是在于他将我们的n-2个碟子缓存到B上,改为了将n- r个碟子转移到B柱上。

    差别即使核心,这个算法的核心,就是计算n个盘子的情况下,r为何值时,能够使得算法最优。

     

    找到了核心,我们现在的任务就明确了,就是对r值的计算。

    这里给出了一个较笨的方法--枚举(不知道各位有没有其他方法)。就是将一定范围内的n与r的所有取值带入,得到满足F(n)为最小值的r的值,记为K[n] = r;

    具体的代码如下:

    [cpp] view plaincopy
     
     
    1. void Init_K(void )  
    2. {  
    3.     int i, k;     
    4.     __int64 temp;     
    5.     __int64 m[Max+1] = {0};       
    6.       
    7.     for( i = 1; i <= Max; i++ )  
    8.     {  
    9.         m[i] = INT_MAX;       
    10.         for( k = 1; k <= i; k++ )  
    11.         {  
    12.             temp = 2*m[i-k] + (__int64)pow(2,k) - 1;      
    13.             if( temp < m[i] )  
    14.             {  
    15.                 m[i] = temp;      
    16.                 K[i] = k;     
    17.                 //printf("K[%d] = %d, m[i] = %d ", i, k, temp );     
    18.             }  
    19.         }  
    20.     }  
    21. }  


    得到各个n对于的r之后,算法将变的非常简单,具体实现如下:

    [cpp] view plaincopy
     
     
    1. #include <stdio.h>  
    2. #include <math.h>  
    3. #define Max 100   
    4. #define INT_MAX 0xfffffffffffffff  
    5. int K[Max+1] = {0};   
    6. int step = 0;     
    7.   
    8. void Hanoi_Four( int n, char a, char b, char c, char d );     
    9. void Hanoi_Three( int n, char a, char b, char c );    
    10. void Move( char x, char y );      
    11.   
    12. void Move( char x, char y )  
    13. {  
    14.     printf("%c --> %c ", x, y); //打印路径,x-->y  
    15. }  
    16.   
    17. void Hanoi_Three( int n, char a, char b, char c )  
    18. {  
    19.     if( n <= 0 )  
    20.         return ;      
    21.       
    22.     step++;     //步骤+1  
    23.       
    24.     if( n == 1 )  
    25.     {  
    26.         Move( a, c );   //将a柱上的一个盘子直接移动到c柱  
    27.         return ;      
    28.     }  
    29.     else  
    30.     {  
    31.         Hanoi_Three( n-1, a, c, b);     //将a上n-1个盘子以c为缓存,全部转移到 b 柱上  
    32.         Move( a, c );                   //将A上留下的第n个盘子,直接转移到 C  柱上  
    33.         Hanoi_Three( n-1, b, a, c);     //将b上的n-1个盘子,以a为缓存,全部转移到 c 柱上  
    34.     }  
    35. }  
    36.   
    37. void Hanoi_Four( int n, char a, char b, char c, char d )  
    38. {  
    39.     if( n <= 0 )  
    40.         return ;      
    41.       
    42.     if( n == 1 )  
    43.     {  
    44.         step++;   
    45.         Move( a, d );     
    46.         return ;      
    47.     }  
    48.     else  
    49.     {  
    50.         int kn = K[n];    
    51.         //printf("kn = %d ", K[n]);      
    52.         Hanoi_Four( n-kn, a, c, d, b );     //用4柱汉诺塔算法把A柱上部分的n- kn个碟子通过C柱和D柱移到B柱上  
    53.         Hanoi_Three( kn, a, c, d );         //用3柱汉诺塔经典算法把A柱上剩余的kn个碟子通过C柱移到D柱上。  
    54.         Hanoi_Four( n-kn, b, a, c, d );     //用4柱汉诺塔算法把B柱上的n-r个碟子通过A柱和C柱移到D柱上  
    55.     }  
    56. }  
    57.   
    58. void Init_K(void )  
    59. {  
    60.     int i, k;     
    61.     __int64 temp;     
    62.     __int64 m[Max+1] = {0};       
    63.       
    64.     for( i = 1; i <= Max; i++ )  
    65.     {  
    66.         m[i] = INT_MAX;       
    67.         for( k = 1; k <= i; k++ )  
    68.         {  
    69.             temp = 2*m[i-k] + (__int64)pow(2,k) - 1;      
    70.             if( temp < m[i] )  
    71.             {  
    72.                 m[i] = temp;      
    73.                 K[i] = k;     
    74.                 //printf("K[%d] = %d, m[i] = %d ", i, k, temp );     
    75.             }  
    76.         }  
    77.     }  
    78. }  
    79.   
    80. int main()  
    81. {  
    82.     int n;    
    83.     Init_K();     
    84.   
    85.     printf("Please enter the number of the Plates:  ");  
    86.     while( scanf("%d", &n) != EOF )  
    87.     {  
    88.         step = 0;     
    89.         Hanoi_Four( n, 'A', 'B', 'C', 'D' );      
    90.         //Hanoi_Three( n, 'A', 'B', 'C' );    
    91.         printf("************************** Total Step: %d ", step );    
    92.   
    93.         printf("Please enter the number of the Plates:  ");  
    94.     }  
    95.       
    96.     return 0;     
    97. }  

     

    到这里,四柱汉诺塔的算法基本讲完了。

    有兴趣的同学,可以继续归纳多柱汉诺塔的实现方法,欢迎交流指导!

     

     

     很多时候,看似合理的背后,其实是一种思维定势。。。

    from: http://blog.csdn.NET/cyh_24/article/details/8075578

  • 相关阅读:
    2018ddctf wp
    装饰器
    python作用域
    闭包
    迭代器
    ord() expected string of length 1, but int found
    pygm2安装问题
    elf逆向入门
    【POJ
    【POJ
  • 原文地址:https://www.cnblogs.com/KingIceMou/p/7000863.html
Copyright © 2011-2022 走看看