递归是一个让无数编程初学者懵逼的概念,偏偏它又是很多高级算法的基础。
递归本身的含义
递归,在计算机科学中是指一种通过 重复 将问题分解为 同类的子问题 而解决问题的方法。简单来说,递归表现为 函数 调用 函数本身。
在知乎看到一个比喻递归的例子,: 什么是递归?
- 李鹏的回答 - 知乎 www.zhihu.com/question/20...
递归最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。
又例如 你浏览器搜索 "递归" 结果页面上又有一行字问你,您是不是要找'递归'
点进去后,又会来到递归页面, 这仿佛是赛博鬼打墙,哈哈哈哈哈哈哈哈!

但递归真正的作用不是无线套娃下去 ,而是帮你解决复杂的问题。
递归: 可以把一个大问题分解成更小的同类型问题 ,直到问题小到可以被直接解决,继而解决到原本的大问题
递归是需要一个终止条件的,当某个终止条件满足时,就不再递归调用啦! 要是你写出来的递归 没终止条件 亦或者说是一直到不了递归条件的话,会导致函数无限调用直到程序崩溃! 大家要注意这点!⚠️⚠️⚠️
那问题又来了,为什么程序无限调用会导致崩溃呢?「 栈溢出问题」「StackOverflowproblem」
- 因为每次调用函数,都会在内存新增一个栈帧,用来保存函数内部的状态,比如函数局部变量的值等等。
- 因为各个函数内部是互相独立的,但栈的空间是有限的,当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。 ⚠️⚠️⚠️如果我们一味的调用函数而不返回,一旦栈被填满后,就会导致 栈溢出「StackOverflow」,程序会直接终止运行!
- 延伸一下,递归过程类似于出栈入栈。如果递归次数过多,栈的深度就需要越深,最后栈容量真的不够装。
递归的特点
实际上,递归有两个显著的特征,终止条件和自身调用:
• 自身调用:原问题可以分解为子问题,子问题和原问题的求解方法是一致的,即都是调用自身的同一个函数。
• 终止条件:递归必须有一个终止的条件,即不能无限循环地调用本身。
递归存在的问题
- 递归调用层级太多,会导致栈溢出的问题。
- 递归重复计算,会导致效率低下。
递归的种类
- 在计算机科学和编程中,根据递归函数的结构和调用方式,可以将递归分为几种主要类型。
. 直接递归 (Direct Recursion)
这是最常见、最直观的递归形式。一个函数直接调用自己。
示例 :计算阶乘

. 间接递归 (Indirect Recursion)
在这种情况下,函数 A 调用函数 B,而函数 B 又反过来调用函数 A,形成一个调用循环。
示例:判断奇偶数
这段代码的目标是判断一个数字是偶数 还是奇数。
- 如果是偶数,
isEven(数字)函数会告诉我们true。 - 如果是奇数,
isEven(数字)函数会告诉我们false。
例如,isEven(2) 应该返回 true,isEven(3) 应该返回 false。 Qwen3-Coder
1. 直接递归 (Direct Recursion)
这是最常见、最直观的递归形式。一个函数直接调用自己。
示例:计算阶乘
javascript
编辑
matlab
1function factorial(n) {
2 // 基准情况 (Base Case): 递归的终止条件
3 if (n <= 1) {
4 return 1;
5 }
6 // 递归步骤 (Recursive Step): 函数调用自身
7 else {
8 return n * factorial(n - 1);
9 }
10}
11
12// factorial(3) -> 3 * factorial(2) -> 3 * 2 * factorial(1) -> 3 * 2 * 1
2. 间接递归 (Indirect Recursion) 2. 间接递归(间接递归)
在这种情况下,函数 A 调用函数 B,而函数 B 又反过来调用函数 A,形成一个调用循环。
示例:判断奇偶数
javascript
编辑
javascript
1function isEven(n) {
2 if (n === 0) {
3 return true;
4 } else {
5 return isOdd(n - 1); // 调用 isOdd
6 }
7}
8
9function isOdd(n) {
10 if (n === 0) {
11 return false;
12 } else {
13 return isEven(n - 1); // 调用 isEven
14 }
15}
16
17// isEven(3) -> isOdd(2) -> isEven(1) -> isOdd(0) -> false
3. 尾递归 (Tail Recursion)
尾递归是一种特殊的直接递归。在函数的最后一步执行递归调用,并且该递归调用是函数返回的表达式,其返回值不需要再参与任何计算。
优点:尾递归在某些编程语言(如 Scheme、Scala)和编译器中可以被优化成循环,从而避免了创建新的栈帧,节省了内存,防止栈溢出。
示例:计算阶乘(非尾递归 vs 尾递归)
-
非尾递归版本:
javascript
编辑
scss1function factorial(n) { 2 if (n <= 1) { 3 return 1; 4 } 5 // 递归调用后还要进行乘法运算,所以不是尾递归 6 return n * factorial(n - 1); 7} -
尾递归版本:
javascript
编辑
scss1function factorialTail(n, accumulator = 1) { // accumulator 用于累积结果 2 if (n <= 1) { 3 return accumulator; 4 } 5 // 递归调用是函数的最后一步,且结果直接返回,是尾递归 6 return factorialTail(n - 1, accumulator * n); 7} 8 9// factorialTail(3, 1) -> factorialTail(2, 3) -> factorialTail(1, 6) -> 6
4. 树形递归 (Tree Recursion)
当一个函数在其递归步骤中调用自身多次时,就形成了树形递归。这种递归的调用过程会形成一棵树状结构,通常会产生大量的重复计算。
用经典的二叉树 来理解树形递归吧
前序排列 (根左右)代码样式

中序排列(左根右)代码样式

后序排列(左右根)代码样式
解释一下: 前半部分就是将 二叉树用代码 表达出来 后半部分用代码将 二叉树 分别用三种方法 遍历出来
递归的经典应用场景
哪些问题我们可以考虑使用递归来解决呢?即递归的应用场景一般有哪些呢?
• 阶乘问题
• 二叉树深度
• 汉诺塔问题
• 斐波那契数列
• 快速排序、归并排序(分治算法也使用递归实现)
• 遍历文件,解析xml文件
厚积薄发 这只是其中的一小步 后续还有更多的知识需要我们去慢慢积累与学习!