10-递归

1. 概述

  • 递归就是方法在内部调用自己本身,和调别起的方法没区别
  • 递归需要遵守的重要规则
    • 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
    • 方法的局部变量是独立的,不会相互影响;但如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
    • 递归必须向退出递归的条件逼近,即该函数所处理的数据规模必须在递减,否则就是无限递归,出现 StackOverflowError
    • 当一个方法执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
  • 一个应用场景:迷宫问题(回溯)

2. 理解“方法调用”过程

  • 当在一个函数运行期间,调用另一个函数时,在运行被调用函数之前,系统需要先完成 3 件事
    • 将所有实参,返回地址等信息传递给被调用函数保存
    • 为被调用函数的局部变量分配存储区
    • 将控制转移到被调函数的入口
  • 从被调用函数返回调用函数之前,系统也应完成 3 件工作
    • 保存被调函数的计算结果
    • 释放被调函数的数据区
    • 依照被调函数保存的返回地址将控制转移到调用函数
  • 当有多个函数构成嵌套调用时,按照"后调用先返回"的原则
    • 上述函数之间的信息传递和控制转移必须通过"栈"来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中
    • 每当调用一个函数,就为它在栈顶分配一个存储区
    • 每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区必在栈顶

补充:https://www.jianshu.com/p/e7a22923867f

  • 当在一个函数运行期间,调用另一个函数时,在运行被调用函数之前,系统需要先完成 3 件事
    • 将所有实参,返回地址等信息传递给被调用函数保存
    • 为被调用函数的局部变量分配存储区
    • 将控制转移到被调函数的入口
  • 从被调用函数返回调用函数之前,系统也应完成 3 件工作
    • 保存被调函数的计算结果
    • 释放被调函数的数据区
    • 依照被调函数保存的返回地址将控制转移到调用函数
  • 当有多个函数构成嵌套调用时,按照"后调用先返回"的原则
    • 上述函数之间的信息传递和控制转移必须通过"栈"来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中
    • 每当调用一个函数,就为它在栈顶分配一个存储区
    • 每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区必在栈顶

补充:https://www.jianshu.com/p/e7a22923867f

3. 案例

3.1 阶乘

public static int factorial(int n) {
    if (n == 1) return 1;
    else return factorial(n-1) * n;
}

3.2 汉诺塔

public static void move(int dish, char x, char y) {
    System.out.printf("[%d]: %c -> %c
", dish, x, y);
}

public static void hanoi(int n, char a, char b, char c) {
    if (n == 1) {
        move(n, a, c);
    } else { // 这 [n-1个盘子] 要看作一个整体
        hanoi(n-1, a, c, b); // 从a - 借助c - 到b
        move(n, a, c);
        hanoi(n-1, b, a, c); // 从b - 借助a - 到c
    }
}

4. 递归能解决什么样的问题

使用递归解决问题的思路:[ 规模为 n 的问题 ] 的解决要借助于 [ 规模为 n-1 的问题 ] 的解决

  • 各种数学问题:八皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题
  • 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
  • 需要用栈解决的问题 // 递归代码比较简洁
原文地址:https://www.cnblogs.com/liujiaqi1101/p/12237680.html