教你精通Java语法之第十二章、递归

简介: 内层函数调用(子集处理)完成,外层函数才能算调用完成。

 目录

一、递归

1.1递归的概念

1.1.1定义

1.1.2原理

1.1.3思路

1.2单路递归

1.2.1阶乘

1.2.2正向输出数字

1.2.3反向输出字符串

1.3多路递归

1.3.1斐波那契数列

1.3.2兔子问题

1.3.3青蛙爬楼梯

1.4汉诺塔问题

1.5猴子吃桃问题

1.6老鼠走迷宫问题

二、递归的时间复杂度


一、递归

1.1递归的概念

1.1.1定义

定义:递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集。

比如单链表递归遍历的例子:

void f(Node node) {
    if(node == null) {
        return;
    }
    println("before:" + node.value)
    f(node.next);
    println("after:" + node.value)
}
image.gif

说明:

    1. 自己调用自己,如果说每个函数对应着一种解决方案,自己调用自己意味着解决方案是一样的(有规律的)。
    2. 每次调用,函数处理的数据会较上次缩减(子集),而且最后会缩减至无需继续递归。
    3. 内层函数调用(子集处理)完成,外层函数才能算调用完成。

    1.1.2原理

    假设链表中有 3 个节点,value 分别为 1,2,3,以上代码的执行流程就类似于下面的伪码

    // 1 -> 2 -> 3 -> null  f(1)
    void f(Node node = 1) {
        println("before:" + node.value) // 1
        void f(Node node = 2) {
            println("before:" + node.value) // 2
            void f(Node node = 3) {
                println("before:" + node.value) // 3
                void f(Node node = null) {
                    if(node == null) {
                        return;
                    }
                }
                println("after:" + node.value) // 3
            }
            println("after:" + node.value) // 2
        }
        println("after:" + node.value) // 1
    }

    image.gif

    1.1.3思路

      1. 确定能否使用递归求解
      2. 推导出递推关系,即父问题与子问题的关系,以及递归的结束条件
      3. 深入到最里层叫做递
      4. 从最里层出来叫做归
      5. 在递的过程中,外层函数内的局部变量(以及方法参数)并未消失,归的时候还可以用到

      1.2单路递归

      1.2.1阶乘

      用递归方法求阶乘

        • 阶乘的定义 $n!= 1⋅2⋅3⋯(n-2)⋅(n-1)⋅n$,其中 n 为自然数,当然 0! = 1

        image.gif编辑

        代码实现:

        private static int fac(int num) {
            if (num < 1) {
                return 1;
            }
            return num * f(num - 1);
        }

        image.gif

        拆解伪码如下,假设 n 初始值为 3:

        f(int num = 3) { // 解决不了,递
            return 3 * f(int num = 2) { // 解决不了,继续递
                return 2 * f(int num = 1) {
                    if (num < 1) { // 可以解决, 开始归
                        return 1;
                    }
                }
            }
        }
        image.gif

        1.2.2正向输出数字

        public static void Print(int num){
                if(num>9){
                    Print(num/10);
                }
                System.out.print(num%10);
            }

        image.gif

        1.2.3反向输出字符串

        用递归反向打印字符串,n 为字符在整个字符串 str 中的索引位置。

          • :n 从 0 开始,每次 n + 1,一直递到 n == str.length() - 1
          • :从 n == str.length() 开始归,从归打印,自然是逆序的image.gif编辑
          public static void Print(String str,int index){
                  if(index<str.length()-1){
                      Print(str,index+1);
                  }
                  //System.out.println(str.substring(index,index+1));
                  System.out.println(str.charAt(index));
              }
          • image.gif

          拆解伪码如下,假设字符串为 "abc"。


          void reversePrint(String str, int index = 0) {
              void reversePrint(String str, int index = 1) {
                  void reversePrint(String str, int index = 2) {
                      void reversePrint(String str, int index = 3) { 
                          if (index == str.length()) {
                              return; // 开始归
                          }
                      }
                      System.out.println(str.charAt(index)); // 打印 c
                  }
                  System.out.println(str.charAt(index)); // 打印 b
              }
              System.out.println(str.charAt(index)); // 打印 a
          }
          image.gif

          1.3多路递归

          1.3.1斐波那契数列

          概念:如果每个递归函数例包含多个自身调用,称之为 multi recursion

          image.gif编辑

          public static int fib(int num) {
              if (num == 0) {
                  return 0;
              }
              if (num == 1) {
                  return 1;
              }
              return fib(num - 1) + fib(num - 2);
          }

          image.gif

          image.gif编辑

          1.3.2兔子问题

          image.gif编辑

            • 第一个月,有一对未成熟的兔子(黑色,注意图中个头较小)
            • 第二个月,它们成熟
            • 第三个月,它们能产下一对新的小兔子(蓝色)
            • 所有兔子遵循相同规律,求第 n 个月的兔子数

            分析

            兔子问题如何与斐波那契联系起来呢?设第 n 个月兔子数为 f(n)

              • f(n) = 上个月兔子数 + 新生的小兔子数
              • 而【新生的小兔子数】实际就是【上个月成熟的兔子数】
              • 因为需要一个月兔子就成熟,所以【上个月成熟的兔子数】也就是【上上个月的兔子数】
              • 上个月兔子数,即 f(n-1)
              • 上上个月的兔子数,即 f(n-2)

              f(n)=f(n-1)+f(n-2);

              因此本质还是斐波那契数列,只是从其第一项开始

              1.3.3青蛙爬楼梯

                • 楼梯有 n 阶
                • 青蛙要爬到楼顶,可以一次跳一阶,也可以一次跳两阶
                • 只能向上跳,问有多少种跳法

                image.gif编辑

                  1.4汉诺塔问题

                  该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

                  image.gif编辑

                  设移动盘子数为n,为了将这n个盘子从A杆移动到C杆,可以做以下三步:

                  (1)以C盘为中介,从A杆将1至n-1号盘移至B杆;

                  (2)将A杆中剩下的第n号盘移至C杆;

                  (3)以A杆为中介;从B杆将1至n-1号盘移至C杆。

                  递归法:

                  实际操作中,只有第二步可直接完成,而第一、三步又成为移动的新问题。以上操作的实质是把移动n个盘子的问题转化为移动n-1个盘,那一、三步如何解决?事实上,上述方法设盘子数为n, n可为任意数,该法同样适用于移动n-1个盘。因此,依据上法,可解决n -1个盘子从A杆移到B杆(第一步)或从B杆移到C杆(第三步)问题。现在,问题由移动n个盘子的操作转化为移动n-2个盘子的操作。依据该原理,层层递推,即可将原问题转化为解决移动n -2、n -3… … 3、2,直到移动1个盘的操作,而移动一个盘的操作是可以直接完成的。

                  //代码实现:
                  public static void Tower(int num,char A,char B,char C){
                          //num表示盘子的个数,A,B,C表示A塔,B塔,C塔
                          if(num==1){
                              System.out.println("移动:"+A+"到"+C);
                          }
                          else{
                              //如果有多个盘子,可以看成两个,最下面的,上面所有的盘子(num-1)
                              //(1.)先移动上面所有的盘子到B盘,借助C盘
                              Tower(num-1,A,C,B);
                              //(2.)把最下面的盘移动到C盘
                              System.out.println("移动:"+A+"到"+C);
                              //(3.)再把B塔上的盘子移动到C盘,借助A盘
                              Tower(num-1,B,A,C);
                          }
                      }

                  image.gif

                  1.5猴子吃桃问题

                  猴子吃桃子问题:     有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个! 以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时, 想再吃时(即还没吃),发现只有1个桃子了。问题:最初共多少个桃子?

                  //代码实现
                    //猴子吃桃问题
                      //day10:1个桃子
                      //day9:(day10+1)*2
                      //day8:(day9+1)*2
                  public static int Peach(int day){
                          if(day<0||day>10){
                              return -1;
                          }
                          if(day==10){
                              return 1;
                          }
                          else if(day>=1&&day<=9){
                              return (Peach(day+1)+1)*2;
                          }
                          return -1;
                      }

                  image.gif

                  1.6老鼠走迷宫问题

                  目标:小球从1-1走到6-5

                  image.gif编辑

                  迷宫问题:

                  1.小球得到的路径,和程序员设置的找路策略有关:找路的上下左右的先后顺序

                  //代码实现
                  public static void main(String[] args) {
                        //思路:
                          //1.设置迷宫,通过二维数组表示int[][] map=new int[8][7];
                        //2.规定:0表示可以走,1表示障碍物
                          int[][] map=new int[8][7];//8行7列
                          for (int i = 0; i < map.length; i++) {
                              for (int j = 0; j < map[i].length; j++) {
                                  //设置最上面一行和最下面一行为1
                                  map[0][j]=1;
                                  map[7][j]=1;
                                  if(i>=1&&i<7){
                                      //设置最右面和最左面一列为1
                                      map[i][0]=1;
                                      map[i][6]=1;
                                  }
                              }
                          }
                          //输出迷宫
                          disPlay(map);
                          //设置障碍物
                          map[2][1]=1;
                          map[2][2]=1;
                          disPlay(map);
                          //找路
                          findWay(map,1,1);
                          disPlay(map);
                      }
                      public static void disPlay(int[][] map){
                          System.out.println("==============================");
                          for (int i = 0; i < map.length; i++) {
                              for (int j = 0; j < map[i].length; j++) {
                                  System.out.print(map[i][j]+" ");
                              }
                              System.out.println();
                          }
                      }
                      public static boolean findWay(int[][] map,int i,int j){
                          //1.findWay方法找出迷宫的路径
                          //2.如果找到,返回true,否则返回false
                          //3.i,j表示老鼠的位置,初始化的位置为(1,1);
                          //4.0表示可以走,1表示障碍物,2表示可以走,3表示走过但是走不通,是一条死路
                          //5.当map[5][6]==2,说明找到了通路,此时就可以结束,否则继续找
                          if(map[6][5]==2){//说明已经找到
                              return true;
                          }
                          else{
                              if(map[i][j]==0){ //表示当前位置可以走
                                  //假定该位置可以走通
                                  map[i][j]=2;
                                  //找路的逻辑:我的:右下左上(逻辑不通找路结果不同)或者【下右上左】
                                  if(findWay(map,i,j+1)){//右
                                      return true;
                                  }
                                  else if(findWay(map,i+1,j)){//下
                                      return true;
                                  }
                                  else if(findWay(map,i,j-1)){//左
                                      return true;
                                  }
                                  else if(findWay(map,i-1,j)){//上
                                      return true;
                                  }
                                  else{
                                      map[i][j]=3;
                                      return false;
                                  }
                              }
                              else{
                                  return false;
                              }
                          }
                      }

                  image.gif

                  2.存在回溯现象

                  //修改上述部分代码
                          //设置障碍物
                          map[3][1]=1;
                          map[3][2]=1;
                        map[2][2]=1;
                          disPlay(map);

                  image.gif


                  二、递归的时间复杂度

                  递归式:                

                  其中

                    • T(n) 是问题的运行时间,n 是数据规模
                    • a 是子问题个数
                    • T(\frac{n}{b}) 是子问题运行时间,每个子问题被拆成原问题数据规模的 \frac{n}{b}
                    • f(n) 是除递归外执行的计算
                    目录
                    相关文章
                    |
                    1月前
                    |
                    Java 程序员
                    深入理解Java匿名内部类的语法和使用场景
                    深入理解Java匿名内部类的语法和使用场景
                    |
                    6天前
                    |
                    缓存 前端开发 Java
                    【前端学java】java基础巩固复习巩固语法练习-工具类的封装(14)
                    【8月更文挑战第10天】java基础巩固,工具类的封装
                    11 1
                    【前端学java】java基础巩固复习巩固语法练习-工具类的封装(14)
                    |
                    7天前
                    |
                    存储 前端开发 JavaScript
                    【前端学JAVA】有手就会!10min快速入门java的基础语法(2)
                    【8月更文挑战第8天】10min快速入门java的基础语法
                    19 2
                    【前端学JAVA】有手就会!10min快速入门java的基础语法(2)
                    |
                    11天前
                    |
                    安全 Java 编译器
                    Java 基础语法-面试题(53道)(基础概念+基础语法+流程控制)
                    Java 基础语法-面试题(53道)(基础概念+基础语法+流程控制)
                    36 18
                    |
                    11天前
                    |
                    Java
                    Java 基础语法-面试题(54-63道)(数组+类+包)
                    Java 基础语法-面试题(54-63道)(数组+类+包)
                    29 16
                    |
                    11天前
                    |
                    存储 Java 编译器
                    Java 基础语法-方法
                    Java 基础语法-方法
                    26 12
                    |
                    8天前
                    |
                    Java API
                    Java语法Java语法
                    【8月更文挑战第12天】Java语法
                    20 3
                    |
                    11天前
                    |
                    Java 关系型数据库 程序员
                    11 Java接口(语法、规范、微观&宏观接口)
                    11 Java接口(语法、规范、微观&宏观接口)
                    31 6
                    |
                    11天前
                    |
                    存储 Java
                    02 Java基础语法(变量+数据类型+运算符)(下)
                    02 Java基础语法(变量+数据类型+运算符)
                    16 5
                    |
                    11天前
                    |
                    存储 Java
                    02 Java基础语法(变量+数据类型+运算符)(上)
                    02 Java基础语法(变量+数据类型+运算符)
                    29 4