递归调用
大家好!这个章节我们来聊聊递归调用这个话题。你可以把递归看作是一个函数自己调用自己,通常是用来解决一些可以分解成更小子问题的问题。递归的应用非常广泛,像是数学中的阶乘、斐波那契数列等,都是递归的经典案例。
简介
有一个很古老的故事是这样讲的:
从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么呢?
从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么呢?
。。。

那我们首先从一个简单的故事开始来理解递归。来看看这个故事。你们听过“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事……”这个故事吗?
除非讲故事的人自己停下来不讲了,不然这个故事可以无限讲下去,原因就是故事嵌套的故事就是故事本身,实际上这个就是语言的递归一个例子。
但上面的例子是没有结束条件的,如果设计到代码中就会陷入无限循环执行的死循环状态,无法为程序的设计人员解决事务。
当涉及解决问题时,递归是一种非常有用的编程技巧,为它设计一个出口,它允许函数调用自身以解决更小规模的问题,直到达到基本情况为止。

其实它是一个经典的递归故事。你看,故事里面讲的是一个故事,而这个故事又在讲自己,结果就可以不断重复下去。这就像是递归调用一样,一个函数不断调用自己,直到某个条件结束。不过,注意这里的故事没有结束条件,如果没有结束条件,它会无限进行下去,就像程序中的死循环一样。在编程中,递归也是类似的。它通过函数自我调用来解决一个小规模的问题,直到达成一个基本情况,才会停止递归。这时候就能够返回结果,不会一直陷入死循环。
递归的基本原则
递归函数通常遵循以下原则:
- 定义基本情况:确定一个或多个输入的特殊情况,当满足这些条件时,递归函数将直接返回结果而不再调用自身。
- 减小问题规模:通过调用自身来解决一个规模更小的问题,这样每次递归调用都在问题规模上取得了进展。也就是需要一个已定义好的规则来使其它非基本的情况转化为基本情况。
- 终止条件:递归函数必须包含能够导致函数不再递归调用的条件,以避免无限递归。
递归的核心思想就是“让问题更小”。当我们遇到一个大问题时,通过递归将问题分解成更小的子问题,一步一步逼近最简单的情形。递归函数通常遵循以下几个原则:定义基本情况:需要明确什么时候递归停止,也就是给定一个或多个输入的特殊条件,如果满足这些条件,递归就停止并返回结果。减小问题规模:每次递归都应该在“问题规模”上取得进展。也就是说,每次调用自身时,问题应该变得更小,这样最终能达到停止递归的基本情况。终止条件:这是递归的关键。如果没有终止条件,递归会陷入无限循环,导致程序崩溃。
递归的使用
数学中有一个比较经典的递归算式就是求阶乘。
一个正整数的阶乘 factorial 是所有小于及等于该数的正整数的积,并且 0 的阶乘为 1。自然数 n 的阶乘写作 n!。
说到递归,最经典的例子就是计算阶乘了。比如,5 的阶乘就是 5乘以4乘以3乘以2乘以1,阶乘的概念本身就可以用递归来定义。特别地,0 的阶乘是 1,这是递归的基本情况。
对阶乘的定义进行分析:
- 首先定义
f(n)为阶乘函数。它的结果就是阶乘计算之后的值。 - 从定义中能得到基本情况为:
f(0) = 1,f(1) = 1 - 其它情况:
f(2) = 2*1 = 2*f(1),f(3) = 3*2*1 = 3*f(2)即f(n) = f(n-1) * n
让我们来看看阶乘的递归定义。比如我们先定义一个阶乘函数 f 接受一个参数 n。它返回阶乘计算之后的值。当 f 函数传入 0 或者 1 的时候是基本情况,这个时候函数返回 1。当传入其他整数 n 的时候,计算结果就是当前数乘以前一个数的阶乘的值。也就是我们在递归调用的时候,输入的数不断减小,直到遇到 1 或 0,递归才会停止。
示例:
def f(n):
"""
实现计算 n 的阶乘
return:n 阶乘计算之后的值
"""
if n == 0 or n == 1:
# 对应基本情况
return 1
return f(n - 1) * n # 对应递归情况
这段代码首先检查输入的数字是否为 0 或 1,如果是,就直接返回 1,进入递归的基本情况。如果输入的数字大于 1,那么就执行递归调用:f(n减1) 乘以 n,不断减小问题的规模,直到到达基本情况。举个例子,如果我们计算 4 的阶乘,函数会这样执行:f(4) 会调用 f(3),f(3) 会调用 f(2),f(2) 会调用 f(1),f(1) 返回 1(基本情况)。然后依次返回:f(2) 返回 2 乘以 1 等于 2,f(3) 返回 3 乘以 2 等于 6,最后 f(4) 返回 4 乘以 6 等于 24。最终,f(4) 会返回 24,就是 4 的阶乘。
注意
1.大多数情况下的递归操作都可以使用循环所替代。
2.在使用递归时,要注意避免陷入无限调用而产生的内存溢出。
在使用递归时,有一些需要特别注意的地方:递归并不总是最优解:虽然递归非常直观,但有时候我们可以用循环来替代递归,避免不必要的函数调用带来的性能问题。避免无限递归:如果递归的终止条件没有写好,递归会一直进行下去,导致内存溢出或者程序崩溃。记得在每个递归函数中都要设定明确的终止条件。
总结
- 递归的基本原则
- 递归的使用
最后来总结一下。递归需要明确的基本情况和终止条件。每次递归调用时,问题的规模要逐渐减小,最终达到基本情况。虽然递归很强大,但在某些场景下,我们可以使用循环来替代递归,从而提高性能。希望大家在理解递归的基础上,能够在实际编程中灵活应用,解决更复杂的问题!