zoukankan      html  css  js  c++  java
  • Java基础——递归

    递归

    一、预备知识

    1、栈内存

    关于栈内存,我们需要了解栈内存管理的细节:

    (1)、栈内存分配的基本单位——栈帧

    a、之前讲过,局部变量(方法的形式参数和方法中定义的变量)存储在栈空间中;

    b、一个方法,当它被调用执行的时候,方法中的局部变量等,才需要在栈空间上存储,当调用结束,立即释放栈空间上占用的内存;

    c、即每一个运行中的方法,都需要占用栈内存中的一片内存空间;

    d、于是,每一个运行的方法,都会在栈上分配一片,只属于该运行中的方法的内存空间——栈帧(Stack Free)

    (2)、栈中内存的基本单位,它的生命周期(何时分配,合适回收)

    a、当一个方法被调用执行的时候,给该运行中的方法,分配栈帧;

    b、当一个方法执行完毕的时候,它对应的栈帧被回收(销毁释放)

    注意:一个方法可以被多次调用,每一次方法的调用(即每一次方法的运行),都会给它分配一个栈帧。(一个方法可以对应多个栈帧,被多次调用)
    
    public class RecursionDemo{
    	public static void main(String[] args){
    		//调用递归方法
    		function(); //报错:StackOverflowError
    	
    	}
    	/*
    		不带递归出口的递归方法(错误的递归)
    	*/
    	public static void function(){
    		//自己调用自己
    		function();
    	}
    	
    
    StackOverflowError产生的原因:
    1、function方法,无限的自己调用自己,每次调用执行,都需要给该方法分配栈帧。
    2、但是栈内存大小有限。
    3、在function方法无限调用自己过程中,每一次方法的执行,都没纸箱完毕(都在调用过程中)
    4、最终,导致栈内存会没有可用内存空间,此时如果,我在向栈申请创建栈帧就会出现StackOverflowError错误
    
    
    Stackoverflow —— 一个很有名、很活跃的程序员问答平台
    

    2、栈的内存空间

    栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    堆区 (heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表

    全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 - 程序结束后有系统释放

    文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放

    程序代码区:存放函数体的二进制代码

    关于栈,我们姑且先了解以上内容
    更多关于栈的详细知识可参见以下博文:
    https://www.cnblogs.com/dwlsxj/p/Stack.html
    https://www.cnblogs.com/George1994/p/6399895.html
    

    二、递归

    1、递归方法的定义

    方法定义中调用方法本身的现象

    2、实现递归需要注意的问题

    (1)、递归一定要有出口!!(递归需要有出口条件,在满足某种特殊条件的情况下,停止递归调用)

    (2)、次数不能太多,否则就会出现Stack overflow

    public class RecursionDemo1{
    	public static void main(String[] args){
    		//调用递归方法
    		function(); //StackOverflowError
    	
    	}
    	/*
    		不带递归出口的递归方法(错误的递归)
    	*/
    	public static void function(){
    		//自己调用自己
    		function();
    	}
    	
    	/*
    		带递归出口的递归方法
    	*/
    	public static void recursion(int n){
    		//递归出口
    		if(n <= 0){
    			//当达到递归出口,不再自己调用自己
    			System.out.println("n <= 0, n = " + n);
    			return;
    		}
    		System.out.println("n > 0, n = " + n);
    		//当未达到递归出口条件时
    		n--;
    		recursion(n);
    	
    	}
    
    }
    

    汉诺塔问题:

    /*
    	1.有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:
          a.每次只能移动一个圆盘。
          b.大盘不能叠在小盘上面。
    
         N在原始的汉诺塔问题中64,如果要完成64个圆盘的搬运,需要的很长很长时间。
        问:最少要移动多少次?如何移?
    
    
    解决思路以N个圆盘为例:
    
          1. 当我们要解决N个圆盘的搬运问题,对于N个圆盘,我们可能无法一次性得到结果,
          于是,我们把N个圆盘的搬运问题, -> 最大一个的圆盘的搬运 & 最大圆盘上面的 n - 1
    
          2. 对于规模为1那个待搬运的最大的圆盘,直接就知道如何搬运(一步搞定)
    
          3. 在2的基础上,只需要,再解决 N - 1个圆盘搬运的问题
    
          总结一下,汉诺塔问题,解决思路(递归算法的核心思想):
          分而治之: 把一个复杂的大规模的问题,分解成若干相似的小规模的子问题,当问题规模,
          足够小的是时候,我们就可以直接得到小规模问题的解,再把小规模问题的解,
          组合起来,——> 大规模问题提的解
    
    
    */
    
    public class RecursionDemo2{
      public static void main(String[] args){
         // 测试我们的hanoi的代码
        hanoi('A', 'C', 'B', 4);
    
        // 测试计数方法
        long result = count(4);
        System.out.println(result);
          
      }
    	
        /*
           计数对于n个圆盘的搬运,一共需要搬运多少次
    
           count(n) 该方法的返回值,n个盘搬运的总次数
       */
      public static long count(int n) {
        // 递归出口
        if (n == 1) {
          return 1;
        }
        //1,hanoi(start, middle, end, n - 1);
        long toMiddle = count(n - 1);
        //2. 对于最大的那个盘,需要搬运一次,从start -> end
        //3. hanoi(middle, end, start, n - 1);
        long toEnd = count(n - 1);
        // 2* count(n - 1) + 1
        return toMiddle + 1 + toEnd;
      }
    
        
        
        /**
        *	用该方法来模拟解决汉诺塔问题中圆盘的搬运
        *	方法参数中,3个char类型的变量,代表3个杆的名称‘A’,‘B’,‘C’
        *	start——>	起始杆
        *	end ——>     目标杆
        *	middle ——>	辅助杆
        *	n ——>	待搬运的圆盘数量
        */
       public static void hanoi(char start, char end, char middle, int n){
           //递归出口,当规模足够小时,退出递归
           if(n == 1){
               System.out.println(start + "——>" + end);
               return;
           }
           
           //按照相同的方式,将最大的圆盘之上的(n-1)个圆盘搬运到辅助杆middle上
           hanoi(start, middle, end, n-1);
           
           //最大的圆盘保留在起始杆start上,且起始杆上只有这个圆盘,对应该圆盘直接搬运到目标杆上,一步到位
           System.out.println(start + "——>" + end);
           
           //再将辅助杆上的(n-1)个圆盘,从middle杆——>end杆(以start杆为辅助杆)
           hanoi(middle, end, start, n-1);
       }
        
        
    }
    
    

    电影院问题:

    /*
    周末晚上你和女朋友去看电影,月黑风高,女朋友悄悄地问你:我们在第几排?电影院太黑,没办法数?怎么办?
          a. 无法直接看到,自己在第几排,但是我可以,问前排的同学,他在第几排
          b. 当依次向前询问,当问到第一排同学的时候,他可以用触觉来判断,比如,摸了一下发现前面没有椅子
    
          递:分解问题              n -> n - 1 -> ...  -> 1(出口条件的情况)
          归: 组合小规模问题的解     n <- n - 1 <- ... 2 <- 1
    
          递推公式:
          location(n) = location(n - 1) + 1
     */
    public class Demo2 {
    
      public static void main(String[] args) {
    
        int location = location(10);
        System.out.println(location);
      }
      /*
          n代表,实际所处的排数
       */
      public static int location(int n) {
    
        //递归出口
        if (n == 1) {
          // 第一排的同学直接知道自己在第一排
          return 1;
        }
        return location(n - 1) + 1;
      }
    
    }
    

    n的阶乘:

    /**
     * 3. 求n的阶乘
     *   1! = 1
     *   100!= 100 * (99 * 98 * ... * 1)
     *   100! = 100 * 99!
     *
     *   如果用f(n) 表示n的阶乘的结果
     *   f(n) = n * f(n - 1)
     */
    public class Demo3 {
    
      public static void main(String[] args) {
        int factor = factor(5);
        System.out.println(factor);
      }
      /*
          求n的阶乘
       */
      public static int factor(int n) {
    
        // 出口条件
        if (n == 1) {
          return 1;
        }
    
        return n * factor(n - 1);
      }
    
    }
    

    不死兔神:

    /* *
     *  4.有一对兔子,从出生后第三个月开始每月生一对兔子,小兔子从第三个月开始每月也生一对兔子,
     *    假如是不死神兔,那么第20个月一共生多少对兔子?
     *
     *   月份           1  2  3  4  5
         兔子对数       1  1  2  3  ...
    
    
          i, i + 1, i + 2 代表任意连续的3个月份
          N(i) 表示第i个月出生的兔子数量
          N(i - 1) 表示第i - 1个月出生的兔子数量
          f(i - 2) 表示第 i - 2个月及之前出生的兔子数量
    
          i                       i + 1                         i + 2
          N(i)     +              N(i)                  =       N(i) * 2
          N(i - 1) +              N(i - 1) + N(i - 1)   =       N(i - 1) * 2 + N(i - 1)
          f(i - 2) +              f(i - 2) + f(i - 2)   =       f(i - 2) * 2 + f(i - 2)
    
        f(i) 表示第i个月兔子的数量
        f(i) = f(i - 1) + f(i - 2)
        f(1) = 1
        f(2) = 1
    
        兔子数量的数列:
           1  2  3  4  5   6  ...
           1  1  2  3  5   8  ...
    
         fab(n) = fab(n - 1) + fab(n - 2)
    
         递归的缺陷: 可能存在重复计算问题
     */
    public class Demo4 {
    
      public static void main(String[] args) {
    
        int count = countRabbit(20);
        System.out.println(count);
    
        //测试利用循环求解斐波那契数列的值
        int fab = fab(20);
        System.out.println(fab);
      }
    
      /*
          求解第n个月的兔子数量
       */
      public static int countRabbit(int n) {
        //出口条件
        if (n == 1 || n == 2) {
          return 1;
        }
    
        return countRabbit(n - 1) + countRabbit(n - 2);
      }
    
    
      /*
          当利用递归求解,出现重复计算的时候,我们可以使用循环来替代递归(这种直接的转化主要针对比较简单的情况)
    
          n:代表要求解的第n项的菲波那切数列的项数
       */
    
      public static int fab(int n) {
        // 就用来存储已经得到结果的第n项的斐波那契数列的值
        int[] tmp = new int[n + 1];
        tmp[1] = 1;
        tmp[2] = 1;
    
        for (int i = 3; i <= n; i++) {
          tmp[i] = tmp[i - 1] + tmp[i - 2];
        }
        return tmp[n];
      }
    
    }
    
    
  • 相关阅读:
    利用反射获取类的所有字段
    System.Data.Entity.Internal.AppConfig 类型初始值设定项引发异常
    sql server 修改列类型
    sql server 删除表字段和字段的约束
    sql server 查找字段上的约束
    ASP.NET MVC自定义路由
    初探pandas——索引和查询数据
    初探pandas——安装和了解pandas数据结构
    初探numpy——numpy常用通用函数
    初探numpy——广播和数组操作函数
  • 原文地址:https://www.cnblogs.com/lcpp/p/13097013.html
Copyright © 2011-2022 走看看