• Conquer and Divide经典例子之汉诺塔问题


    递归是许多经典算法的backbone, 是一种常用的高效的编程策略。简单的几行代码就能把一团遭的问题迎刃而解。这篇博客主要通过解决汉诺塔问题来理解递归的精髓。

    汉诺塔问题简介:
    在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片,1. 一次只移动一片; 2. 不管在哪根针上,小片必在大片上面。当所有的金片都从梵天穿好的那根针上移到另外一概针上时,世界就将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。



    我们首先假设要把n个金片从宝针‘A’移动宝针‘B’,还有有另外一根'V'可以借用。
     
    如果n = 1,那么直接从 A->B 即可。
    如果n = 2,那么先把A最上面(第一个)金片移动到V,暂且放一下,然后第二个金片移动到B目的地,接着再把V上面的移动到B目的地,所以移动的次序是 A->V,  A->B,  V-B。
    如果n = 3,可以这么考虑: 先不看出发地A最下面一个最大的金片,先解决怎么把上面两个移走,方法我们已经知道了,但是移到哪里呢?只有B, V可选,B肯定是不行,因为B是目的地,那么只能是通过B先把两个金片移动到V(A->B, A->V, B->V),然后最下面那个最大的金片移动到目的地B(A->B)。注意这时B上面仅有这个最大的金片,A上面空的,V上面有两个金片。接着把V上面两个金片借由A移动到B(V->A, V->B, A->B)。
    如果n = 4, 同样的不考虑最大的一个,先把前三个移动到V,使用上面介绍的移动次序。
    以此类推(其实 n = 2时,也可以看作是先解决最上面一个的问题,然后才是把最下面的金片移动到目的地)
    通过分析,发现解决n个金片的方法是先借由B,把n-1个移动到V,然后把A最下面一个移动到B,在然后借由A把V上面n-1移动到B。

    用java实现如下
    // in java
    public class Hanoi{
        private static int count = 0;
        public static void move(char src, char des, int n){
            System.out.println("plate:"+ src +" pillar"+"->"+des+" pillar");
            count++;
    
    
        }
    
        public static void hanoiSolver(char src, char via, char des, int n){
            if (n==1){
                move(src, des, n);
            } else{
                hanoiSolver(src, des, via, n-1);
                move(src, des, n);
                hanoiSolver(via, src, des, n-1);
            }
        }
    
        public static void main(String[] args) {
            char src = 'A'; //source pillar
            char des = 'B'; //detination pillar
            char via = 'V'; //via pillar
            int n = 3;// the numer of plates
            hanoiSolver(src, via, des, n);
            System.out.println("the total number of moves is "+count);
        }
    }


     
    仔细分析这个算法,可以学习到:
    1. 初中高中大学数学解题中常常用到的归纳总结。一个看似复杂至极的问题,先心里不要慌,初步分析前面几个,看到规律,推而广之。
    2. 计算过程中的调用自身函数,形成重入,并因此而把复杂问题化解为相对简单或已经知道答案的小问题。这也不经我想到解数列题的各种方法。(对不起,高中是博主学习数学最认真的时候)
    3. 在递归中必须有明确递归结束条件称递归出口,否则就是死循环啦。如本问题中当n=1时,就是退出递归的时候。

    故事中所说的如果成功得把金片移动结束,世界就灰飞烟灭,这是真的吗?我们来小研究下:
    假设f(n)为n个盘子要移动的次数。
    那么根据上面分析 f(n + 1) = f(n) + 1 +f(n) = 2*f(n)+1
    变换为  [f(n + 1) + 1] = 2*[f(n) + 1], 经典等比数列
    因为f(1) = 1,所以可得
    f(n) = 2^n - 1。
    f(64)= 2^64-1,
    所以一共要移动 2^64-1次, 假使一秒钟移动一次,不吃不睡觉,一共要移动2.1350398e+14天,584,942,417,355年。

    Take-away Tip:
    递归Recursive 就是在程序中调用自身,形成重入,把复杂问题层层简化成一个已然解决的问题,
    并在一定条件下退出递归。


  • 相关阅读:
    AIoT 2020 年分析
    TensorFlow解析常量、变量和占位符
    TensorFlow编程结构
    对端边缘云网络计算模式:透明计算、移动边缘计算、雾计算和Cloudlet
    Harmony生命周期
    立体显示与BCN双稳态手性向列相
    显示技术示例
    SystemML大规模机器学习,优化算子融合方案的研究
    改天有空看看 1
    gradle 123123
  • 原文地址:https://www.cnblogs.com/guanghuiz/p/3721355.html
Copyright © 2020-2023  润新知