java递归算法的理解,经典算法,优缺点

1,117 阅读7分钟

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

一、 什么是递归

  • 递归就是一个程序或函数在其中定义或说明有之间或者间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大的减少了程序的代码量,递归的能力在于用有限的语句来定义对象的无限集合,一般来说,递归需要边界条件,递归前进段和递归返回段,当边界条件不满足时,递归前进,当边界条件满足时,递归返回。 ==总结一句话:就是自己调用自己,但是要有递归出口,要不就陷入死循环了==
  • 递归通俗理解: 假设你在一个电影院,你想知道自己坐在哪一排,但是前面人很多,你懒得去数了,于是你问前一排的人「你坐在哪一排?」,这样前面的人 (代号 A) 回答你以后,你就知道自己在哪一排了——只要把 A 的答案加一,就是自己所在的排了,不料 A 比你还懒,他也不想数,于是他也问他前面的人 B「你坐在哪一排?」,这样 A 可以用和你一模一样的步骤知道自己所在的排(==自己调用自己==)。然后 B 也如法炮制,==直到他们这一串人问到了最前面的一排(或者说问到了知道自己是哪一排的人,预示着调用结束 递归的出口)==,第一排的人告诉问问题的人「我在第一排」,最后大家就都知道自己在哪一排了

二、 递归的优缺点

2.1 递归的优点

代码更简洁清晰,可读性好

2.2 递归的缺点

由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。

三、递归使用的注意事项

使用递归需要满足以下两种条件:

  1. 有反复执行的过程(自己调用自己)
  2. 有跳出反复执行过程的条件(递归出口)

四、经典的递归算法(实际应用)

4.1 求阶乘 ,例如求5的阶乘 代码实现如下:

求5的阶乘的数学公式:5×4×3×2×1

   @Test
    public void t1() {
        System.out.println(jieCheng(5));

    }
    public int jieCheng(int a) {
        //a>1 为递归出口
        if (a > 1) {
                      //jiecheng(a-1) 为自己调用自己    
            return a * jieCheng(a - 1);
        } else {
            return a;
        }
    }

4.2 求 1+2+3+4+...+100的和

  @Test
    public void t2() {
        // 结果为5050
        // System.out.println(sum(100));
        System.out.println(sum2(1, 100));
    }

    /**
     * 方法一
     *计算从1到  max 的一次加1的和
     * @param max
     * @return
     */
    public int sum(int max) {
        if (max > 1) {
            return max + sum(max - 1);
        } else {
            return max;
        }

    }

    /**
     * 方法二
     * 计算从min 到 max的一次加1 的和
     * 这相较方法1更有普适性
     * @param min
     * @param max
     * @return
     */
    public int sum2(int min, int max) {
        if (min < max) {
            return min + sum2(min += 1, max);
        } else {
            return min;
        }

    }

4.3 有一列数 1,1,2,3,5,8,13,21,34...,求用递归算出第30个数是多少

本道题的变种:

  1. 兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子?
  2. 斐波那契数列
@Test
    public void t3() {
        // 结果: 832040
        System.out.println(count(30));
    }


    public int count(int i) {
        // 递归出口
        if (i == 1 || i == 2) {
            return 1;
        } else {
            // 自己调用自己
            return count(i - 1) + count(i - 2);
        }
    }

4.4 爬楼梯算法:已知一个楼梯有n个台阶,每次可以选择迈上一个或者两个台阶,求走完一共有多少种不同的走法

分析一下这个算法: A:如果有0个台阶,那么有0种走法,这个不用多说; B:如果有1个台阶,那么有1种走法; C:如果有2个台阶,那么有2种走法(一次走1个,走两次;一次走两个);

  @Test
    public void t4(){
        // 结果:8
        System.out.println(climbStairs(5));
    }
    public int climbStairs(int n ){
        // 递归的出口
        if(n<=0){
            return 0;
        }
        // 递归的出口
        if(n==1){
            return 1;
         }
        // 递归的出口
        if(n==2){
           return  2;
        }
        // 自己调用自己
        return climbStairs(n-1)+climbStairs(n-2);

    }

4.5 汉诺塔

汉诺塔问题是由很多放置在三个塔座上的盘子组成的一个古老的难题,如下图所示,所有的盘子的直径是不同的。并且盘子中央都有一个洞使得它刚好可以放在塔座上,所有的盘子刚开始都是在a座上,这个难题的目标是将左右的盘子都从塔座a,移到塔座c上,每次只可以移动一个盘子,并且任何一个盘子都不可以放置在比它小的盘子上, 在这里插入图片描述 这个问题可以先从简单的方面想,然后一步一步出发,假设有2个盘子,盘子的大小我按照阿拉伯数字命名。从小到大,如果a上面有两个盘子,分别是1,2那么我们只需要把1的盘子移到b上面,然后把2的盘子移到c上面,最后把b上面的盘子1移动到c上面就可以了,这样两个盘子的问题就解决了, 如果是三个盘子呢?我们一样的来命名1,2,3,假设先把1的盘子移动到c的上面,然后把2的盘子移动到b上面,这样目前就是a上面的是3,b上面的是2,c上面的是1,然后将c上面的盘子移动到b上面,继续把a的盘子移动到c上面,这样的话,目前就是b上面有1,2,c上面的有3,现在答案已经很清楚了,将b上面的盘子移动到a上面,然后第二个盘子移动到c上面,最后a的盘子移动到c上面,这样问题就解决了, 但是如果有四个,五个,n个盘子呢?这个时候递归的思想就很好的解决这样的问题了,当只有两个盘子的时候,我们只需要将b塔座作为中介,将盘子1放到中介b上面,然后将盘子2放到c上面,最后将b上面的盘子1移动到c盘就可以了

所以无论多少盘子,我们都将其看做只有两个盘子,假设n个盘子在a的上面,我们将其看做只有两个盘子,只有(n-1)和n这两个盘子, 先将a上面的n-1的哪一个盘子放到塔座b上面,然后将第n的盘子放到目标塔上面, 然后a的上面为空,看做中间塔,b上面的有n-1个盘子,将第n-2以下的盘子看成一个盘子,放到中间a塔上面,然后将第n-1的盘子放到c上面, 这是发现a上面有n-2个盘子,b上面为空,按照上面的方式以此类推,直到全部放到冰箱里面

简单来说:

  1. 从初始塔a上移动到包含n-1个盘子到c上面
  2. 将a上面剩下的一个盘子,放到c上面
  3. 然后假设b上面有n-1个盘子,然后将它看成为n继续将看成n的盘子中间的n-1个盘子放到a上面
  4. 将b上面剩下的那个盘子放到c上面。 …
 public static void main(String[] args) {

        move(3,"A","B","C");
    }

    /**
     * 汉诺塔问题
     *
     * @param dish 盘子个数(也表示名称)
     * @param from 初始塔座
     * @param temp 中介塔座
     * @param to   目标塔座
     */
    public static void move(int dish, String from, String temp, String to) {
        if (dish == 1) {//圆盘只有一个的时候 将其从a移动到c
            System.out.println("将盘子" + dish + "从塔座" + from + "移动到目标塔座" + to);
        }else {
            move(dish-1,from,to,temp);//a为初始塔座,b为目标塔座,c为中介塔座
            System.out.println("将盘子"+dish+"从塔座"+from+"移动到目标"+to);//把a上面的最下面的一个盘子移到c上面
            move(dish-1,temp,from,to);//b为初始塔座,c为目标塔座,a为中介塔座
        }
    }

五、递归在实际项目中的应用

java 递归实现权限树(菜单树)点击打开

参考链接: java递归算法(一)——详解以及几个经典示例 包含分治算法 Java实现简单的递归操作 Java递归解决“九连环”公式