一句话解释就是函数调用函数自身。
如果当前问题只是一个简单问题——解决它。
如果不是——将其分为可以使用相同策略的子问题,直到分解为可以解决的简单问题。这个过程就是函数调用自身的过程。
补充说明:递归并非问题的最优解,可以根据时间复杂度和空间复杂度分析决定是否选用递归解答。
递归和迭代在分析问题的思路上可能会有相似的地方,但是迭代算法是基于显示循环构造的。
因为递归函数中一定包含了函数的自我调用,这就意味着,在实际解题过程中,递归函数将持续重复调用自身直到满足某些条件(比如达到简单问题)才能返回结果。
所以在这个持续自我调用过程中,每次调用,都会将当前函数作为一个对象保存在堆栈空间中,一直到到达Basic case。到达Basic case后,开始依次返回结果,同时释放堆栈空间。在这个过程中,堆栈的LIFO的特点很好的发挥作用。
在递归调用过程中,每次调用,函数都有其自身的上下文。所以,如果需要使某一种对象贯穿整个递归过程,有两种方法:
(1)将该对象作为递归函数的参数
(2)将该对象定义为全局变量。
举例说明:累加计算1-10,当前数current_number和累加值accumulated_sum需要贯穿整个递归过程。
#方法1,将sum作为递归函数的参数
def sum_recursive(current_number, accumulated_sum):
# Base case
# Return the final state
if current_number == 11:
return accumulated_sum
# Recursive case
else:
return sum_recursive(current_number + 1, accumulated_sum + current_number)
>>> sum_recursive(1, 0)
55
#方法2:全局变量
current_number = 1
accumulated_sum = 0
def sum_recursive():
global current_number
global accumulated_sum
# Base case
if current_number == 11:
return accumulated_sum
# Recursive case
else:
accumulated_sum = accumulated_sum + current_number
current_number = current_number + 1
return sum_recursive()
两种方法的运行结果都是:
>>> sum_recursive()
55
在递归过程中,可能会出现重复计算的问题,可以使用记忆化的方式来避免。
来看一个例子:Fibonacci数
def fibonacci_recursive(n):
print("Calculating F", "(", n, ")", sep="", end=", ")
# Base case
if n == 0:
return 0
elif n == 1:
return 1
# Recursive case
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
res = fibonacci_recursive(5)
print("\n----")
print(res)
运行结果:
Calculating F(5), Calculating F(4), Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0), Calculating F(1), Calculating F(2), Calculating F(1), Calculating F(0), Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0), Calculating F(1),
----
5
从运行结果中可以看到,递归函数确实存在重复计算的问题。
加入辅助记忆化的哈希表可以解决重复计算的问题:
def fib(n):
cache = {}
def fibonacci_recursive(n):
if n in cache:
return cache[n]
print("Calculating F", "(", n, ")", sep="", end=", ")
if n < 2:
result = n
else:
result = fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
# put result in cache for later reference.
cache[n] = result
return result
return fibonacci_recursive(n)
res = fib(5)
print("\n----")
print(res)
运行结果:
Calculating F(5), Calculating F(4), Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0),
----
5
从结果看,每一个n只计算了一次。所以,这种方法实际上就是牺牲了多余的内存空间,来达到提高运算的目的。
除了上述使用哈希表,类似的,可以使用装饰器来达到相同效果,这里介绍一个很好用的装饰器:lru_cache。
lru_cache是一个为函数提供缓存功能的装饰器,在下次以相同参数调用时直接返回上一次的结果。如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。
代码如下:
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_recursive(n):
print("Calculating F", "(", n, ")", sep="", end=", ")
# Base case
if n == 0:
return 0
elif n == 1:
return 1
# Recursive case
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
res = fibonacci_recursive(5)
print("\n----")
print(res)
运行结果:
Calculating F(5), Calculating F(4), Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0),
----
5
递归中的数据结构通常都为可递归的可变数据结构。比如list,所以在递归过程中如果频繁操作list,如进行切片操作,则会造成每次切片都会有一个新的变量产生,极大的浪费资源。
以下是递归的一些练习题,有原题地址,部分练习题我在以前的文章里发过,点击链接均可以查看。这些题都可以尝试使用递归来进行解题,但并不代表递归一定是最优的选择。练习的时候可以尝试其他解题思路。
题目 | 博客链接 |
---|---|
反转字符串 | 链接 |
两两交换链表中的节点 | 链接 |
反转链表 | 链接 |
斐波那契数 | 链接 |
二叉树的最大深度 | 链接 |
Pow(x,n) | 链接 |
合并两个有序链表 | 链接 |
第k个语法符号 | 链接 |