zoukankan      html  css  js  c++  java
  • 汉诺塔--递归和非递归 Java实现

    设定有三个柱A,B,C,目标是将A上面的盘子全部移动到C上面(合法移动)

    递归算法

    这是一道递归方法的经典题目,乍一想还挺难理清头绪的,我们不妨先从简单的入手。

    假设 n = 1,只有一个盘子,很简单,直接把它从 A 中拿出来,移到 C 上;

    如果n = 2 呢?这时候我们就要借助 B 了,因为小盘子必须时刻都在大盘子上面,共需要 4 步。
    image
    如果 n > 2 呢?思路和上面是一样的,我们把 n 个盘子也看成两个部分,一部分有 1 个盘子,另一部分有 n - 1 个盘子。
    image
    观察上图,你可能会问:“那 n - 1 个盘子是怎么从 A 移到 C 的呢?”

    注意,当你在思考这个问题的时候,就将最初的 n 个盘子从 A 移到 C 的问题,转化成了将 n - 1 个盘子从 A 移到 C 的问题, 依次类推,直至转化成 1 个盘子的问题时,问题也就解决了。这就是分治的思想。

    实现分治思想的常用方法就是递归。不难发现,如果原问题可以分解成若干个与原问题结构相同但规模较小的子问题时,往往可以用递归的方法解决。具体解决办法如下:

    • n = 1 时,直接把盘子从 A 移到 C;
    • n > 1 时,
      先把上面 n - 1 个盘子从 A 移到 B(子问题,递归);
      再将最大的盘子从 A 移到 C;
      再将 B 上 n - 1 个盘子从 B 移到 C(子问题,递归)。

    代码实现

    List实现

    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
            move(A.size(), A, B,C);
    
        }
    
        public void move(int n,List<Integer> A, List<Integer> B, List<Integer> C){
            if (n == 1){
                C.add(A.get(A.size()-1));
                A.remove(A.size()-1);
                return;
            }
    
            //从A->B
            move(n-1, A, C, B);
            //从A->C
            C.add(A.get(A.size()-1));
            A.remove(A.size()-1);
            //从B->C
            move(n-1, B, A, C);
    
        }
    

    栈实现

     public static void hanota(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C) {
            move(A.size(), A, B,C);
    
        }
    
        public static void move(int n, Stack<Integer> A, Stack<Integer> B, Stack<Integer> C){
            if (n == 1){
                //A->C
                C.push(A.pop());
                return;
            }
    
            //从A->B
            move(n-1, A, C, B);
            //从A->C
            C.push(A.pop());
            //从B->C
            move(n-1, B, A, C);
    
        }
    

    算法分析

    • 时间复杂度:O(2^n-1)一共需要移动的次数。
    • 空间复杂度:O(1)。

    非递归算法

    找规律

    请3步一组观察规律:

    数量为3时:

    1. A->C
    2. A->B
    3. C->B
    
    4. A->C
    5. B->A
    6. B->C
    
    7. A->C
    

    数量为4时:

    1. A->B
    2. A->C
    3. B->C
    
    4. A->B
    5. C->A
    6. C->B
    
    7. A->B
    8. A->C
    9. B->C
    
    10. B->A
    11. C->A
    12. B->C
    
    13. A->B
    14. A->C
    15. B->C
    

    自行观察规律,然后看完算法和代码后验证一遍规律

    规律
    当盘子数为奇数时:
    依次按照下面顺序进行合法的交换,直到C上盘子数=总盘子数
    A C
    A B
    B C
    
    当盘子数为偶数时:
    依次按照下面顺序进行合法的交换,直到C上盘子数=总盘子数
    A B
    A C
    B C
    

    算法实现原理

    Algorithm:
    While size of Destination is less not equal to n
    do
    {
    If num of disks is even then
    
           Make legal move between Source and Auxilary
           Make the legal move between pegs Source and Destination
    
    else
    
            Make the legal move between pegs Source and Destination  
            Make the legal move between pegs Source and Auxilary
    
    endif
    
    Make legal move between pegs Auxilary and Destination
    
    
    
    }
    end While
    

    注:Source为A柱,Auxilary为辅助柱(B柱),Destination为目标柱(C柱)

    翻译版
    Algorithm:
    While 当C柱上的盘子数小于总的盘子数的时候
    do
    {
        if 盘子数是偶数:
    
           A->B(合法移动)
           A->C(合法移动)
    
    else 盘子数是奇数:
    
            A->C(合法移动) 
            A->B(合法移动)
    
    endif
    
    B->C(合法移动)
    
    
    
    }
    end While
    

    合法移动:小的盘子移动到大的盘子上,请仔细体会

    代码实现

    /**
         * 返回1:表示A->B   返回2:表示B->A  返回0:两个都是空的  其他;-1
         * @param A
         * @param B
         * @return
         */
        public static int legalMove(Stack<Integer> A,Stack<Integer> B){
            int a,b;
            try {
                a = Integer.parseInt(A.peek().toString());
            }
            catch(EmptyStackException e){
                a = 0;
            }
            try {
                b = Integer.parseInt(B.peek().toString());
            }
            catch(EmptyStackException e){
                b = 0;
            }
            //移动的两个柱子上都是空的
            if(a==b) {
                return 0;
            }
            if(a == 0)
            {
                //A空了  B ->A
                A.push(B.pop());
                return 2;
            }
            else if(b == 0)
            {
                //柱子B是空得  逆序 A->B
                B.push(A.pop());
                return 1;
            }
    
            if(a<b)
            {
                //A最上面的 比 B的小,A->B是合法的
                B.push(A.pop());
                return 1;
            }
            else if(a > b)
            {
                //B最上面的罗盘 比 A 的小,B->A是合法的
                A.push(B.pop());
                return 2;
            }
            return -1;
        }
    
        public static void main(String[] args) {
            int stepNumber = 0;
            int status = 0;
            try {
                Stack A = new Stack();
                Stack B = new Stack();
                Stack C = new Stack();
    
                System.out.println("输入数量 : ");
                BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
                int n = Integer.parseInt(input.readLine());
                if(n<=0)
                {
                    System.out.println("输入有误!");
                    System.exit(1);
                }
                //元素进栈
                for(int i=n; i>0; i--){
    
                    A.push(i);
                }
                //判断奇偶
                int m = n%2;
                do {
                    if(m==1)
                    {
                        //输入为奇数时
                        if((status = legalMove(A,C)) == 1){
    
                            System.out.println((++stepNumber) + ". A->C");
                        }
                        else if (status == 2){
    
                            System.out.println((++stepNumber) + ". C->A");
                        }
    
                        if((status = legalMove(A,B)) == 1){
    
                            System.out.println((++stepNumber) + ". A->B");
                        }
                        else if(status == 2){
    
                            System.out.println((++stepNumber) + ". B->A");
                        }
                        else{
    
                            break;
                        }
                    }
    
                    else
                    {
                        //偶数时
                        if((status = legalMove(A,B)) == 1){
    
                            System.out.println((++stepNumber) + ". A->B");
                        }
                        else if (status == 2){
    
                            System.out.println((++stepNumber) + ". B->A");
                        }
    
                        if((status = legalMove(A,C)) == 1){
    
                            System.out.println((++stepNumber) + ". A->C");
                        }
                        else if(status == 2){
    
                            System.out.println((++stepNumber) + ". C->A");
                        }
    
                    }
    
                    if((status = legalMove(B,C)) == 1){
    
                        System.out.println((++stepNumber) + ". B->C");
                    }
                    else if(status == 2){
    
                        System.out.println((++stepNumber) + ". C->B");
                    }
                }while(C.size()!=n);
                System.out.println("-----------------------");
            }
    
            catch (Exception e){
            }
        }
    

    算法分析

    • 时间复杂度: O((2^n - 1)/3),等同于 O(2^n)

    声明

    递归算法资料参考于:LeetCode上Pumpkin大佬的题解
    递归算法Java代码参考于:LeetCode上雨辰大佬的题解
    非递归算法来自:Blogger大佬 shaikzakir3149@gmail.com
  • 相关阅读:
    疯狂Java学习笔记(008)
    CSS-链接
    css学习:css样式的引入
    两个文本根据索引key关联合并,将无关联信息单独输出
    三 数据挖掘算法
    二、数据挖掘
    vsftp虚拟账户设置
    python3在linux下的编码错误
    python 一个根据正则判断输入的函数
    python excel读写与dict转换
  • 原文地址:https://www.cnblogs.com/erkye/p/12390866.html
Copyright © 2011-2022 走看看