一、递归的概念
在编程中,我们常常会遇到一个概念:递归。
递归是一个函数调用自身来解决问题的过程。你可以把它看作是“自我重复”的方法,用来分解复杂问题。
1. 举个例子:
想象你在一个楼梯上,每个台阶都有一个编号。如果你站在某个台阶上,要想知道距离地面多少个台阶,你可以做两件事:
- 看看自己前面(一个台阶)的台阶距离地面有多远。
- 然后,加上一个台阶的高度。
这个过程会一直重复下去,直到你到达楼梯的最底部,显然最底部的台阶距离地面是0。
这就是递归的一个典型应用:每个步骤都依赖于自己前一个步骤的结果。
递归的逻辑图
2. 数学上的递归:
比如,计算阶乘(n!)就是一个递归的例子。阶乘的定义是:
- 0! = 1
- n! = n * (n-1)!
也就是说,n的阶乘等于n乘以(n-1)的阶乘,直到n=0时返回1。
3. 递归的关键点:
- 基准条件:递归必须要有一个停止的条件,通常是一个简单的、能直接解决的问题(比如上面提到的0! = 1)。
- 递归条件:问题要被分解为一个更小的子问题,直到基准条件触发。
4. 递归的实际代码:
def factorial(n):
if n == 0: # 基准条件
return 1
else:
return n * factorial(n - 1) # 递归条件
在这个例子中,factorial
函数会调用自己,直到 n
减小到 0。每一次递归都会计算一个较小的子问题,直到最简单的情况被解决。
递归的一个重要特点是它可以让程序代码更加简洁,尤其在处理一些分解式问题时非常有效,比如树的遍历、斐波那契数列等。
5. 递归的核心概念:
- 分解问题:递归通过将一个大问题分解为多个小问题来解决。每个小问题都可以通过相同的方式继续分解,直到它变得足够简单,可以直接解决。
- 回归(返回):递归过程需要一个返回的过程,当问题被分解到足够简单时,递归会逐步“回退”,并汇总答案。
6. 递归的结构:
- 基准条件:递归必须有一个停止条件。没有停止条件的话,递归会无限进行下去,导致栈溢出错误。
- 递归步骤:每次递归调用时,问题要缩小或简化(一般通过参数变化来实现)。
7. 经典的递归:斐波那契数列
斐波那契数列是一个由意大利数学家斐波那契(Fibonacci)提出的数列,定义为:从第三项开始,每一项等于前两项之和。其递推公式为:
- F(0) = 0
- F(1) = 1
- F(n) = F(n-1) + F(n-2) (n ≥ 2)
例如,斐波那契数列的前几个数是:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
该数列在自然界中有许多应用,如植物的叶片排列、花瓣数目等。此外,斐波那契数列还在计算机科学中有重要应用,如动态规划、递归算法等。通过递归或迭代方式可以计算斐波那契数列的第n项,且其具有很高的数学和计算机理论价值。
递归实现斐波那契数列:
def fibonacci(n):
# 基准条件
if n == 0:
return 0
elif n == 1:
return 1
# 递归条件
else:
return fibonacci(n-1) + fibonacci(n-2)
# 测试递归函数
print(fibonacci(5)) # 输出 5
在这个示例中:
- 基准条件:当
n
为 0 或 1 时,直接返回结果(0 或 1)。 - 递归步骤:对于其他的
n
,通过fibonacci(n-1)
和fibonacci(n-2)
来计算。
比如,调用 fibonacci(5)
时,执行的步骤是:
fibonacci(5) = fibonacci(4) + fibonacci(3)
fibonacci(4) = fibonacci(3) + fibonacci(2)
fibonacci(3) = fibonacci(2) + fibonacci(1)
fibonacci(2) = fibonacci(1) + fibonacci(0)
最终结果是 5。
有些人不理解为什么最终结果是5 不应该是0 或者1 吗?
这个问题问得很好!
实际上 递归函数自被使用后,它就有可能不止返回一次。比如 上面的这个例子,如果我们在返回1 的前面,加上“ print('return')” ,也就是打印一个标记,那我们在运行后就会发现:
你可以看到5次返回,每次都是返回1, 所以最终返回的值是5个1的聚合,就是5.
这种递归方式虽然简洁,但效率较低,因为许多子问题被重复计算了多次。
如果我们在每个返回前都打印一个字符串return: “ print('return')”,我们会发现总共有15个返回。
上面的这些问题,我们可以通过 动态规划 或 记忆化 方法来优化它。
8. 递归优化:记忆化
记忆化是一种优化递归算法的方法,通过缓存已经计算过的结果,避免重复计算。我们可以通过字典或数组来缓存每次计算的结果。
def fibonacci_memo(n, memo={}):
if n in memo:
return memo[n] #缓存放在这个字典(数组)里面,当下次有同样的n,不用计算了
if n == 0:
return 0
elif n == 1:
return 1
else:
result = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
memo[n] = result
return result
# 测试优化后的函数
print(fibonacci_memo(5)) # 输出 5
在这个版本中,我们引入了一个 memo
字典,它会缓存已经计算过的 fibonacci(n)
值。这样,当我们再次遇到相同的 n
时,就可以直接返回缓存的结果,避免重复计算。
二、递归的应用:
递归不仅仅用于数学问题,还广泛应用于很多领域,尤其是树形结构的遍历和分治算法。
递归在计算机科学和算法中有许多典型的应用,特别是在处理结构化数据(如树、图)或分治问题时非常有效。下面是一些常见的递归应用: