通俗讲:数据结构递归思想
脑容量有限,拒绝花里胡哨
一个递归求阶乘的例子
#如5的阶乘 f(6)=6*5*4*3*2*1
def f(int n) {
if n <= 0 : return 1
return n * f(n - 1)
}
如果没有特殊说明,下文都是拿此例子说事
通俗讲:递归
- 递归就是函数内调用自己
f(n - 1)
- 一个问题可以分解成具有相同解决思路的子问题才可以使用递归
- 递归函数必须有结束出口:
n <= 9
,否则你就等着扣工资吧(滑稽)! - 函数内调用时参数一致,如调用
f()
是不正确的(f 函数有且只有一个参数) - 递归方式可以转换成迭代(遍历)方式
return f(n-1)
会继续调用递归,return 1
会终止当前递归函数(即结束出口)
记住以上黑体字,你就能解决大部分的递归问题了
递归递归,先递后归
递归的优点和缺点
优点
-
代码简洁
-
易于理解,如在树遍历中,递归的实现明显比循环简单。
缺点
- 每次调用都需要在内存栈中分配空间以保存参数,返回值和临时变量,降低了效率
- 递归的分解的多个小问题存在重叠的部分,即存在重复计算
- 栈空间是有限的,当调用的次数太多,可能会超出栈的容量,造成调用栈溢出
综合各方面,迭代的效率更佳,但有些问题用迭代处理更复杂
用迭代思想去思考递归
循环结束条件
-> 递归结束出口循环体
-> 处理相同解决方案的子问题进行下一次循环
->返回调用递归函数
解题心得
- 一定不要试图跟踪大型递归的过程,否则会你让头脑炸裂,甚至怀疑人生
- 在调用递归的时候利用整体思想,默认f(n)这个整体已经被求出来了,至于怎么求的由计算机来回溯求出。如上面的求阶乘的例子中
当n=6时 6 * f(5)
就默认f(5)=5*4*3*2*1=120
已经在草稿纸算过 - 解题时要清楚递归函数功能、结束出口、要处理的子问题是什么、函数调用位置和次数
如在求阶乘中:函数功能就是求n的阶乘并返回,结束出口就是n<=1
,要处理的子问题就是用当前数乘以 n-1的阶乘
,递归调用位置为在返回的时候直接调用一次即可
在有触发结束递归的情况下,递归调用=子问题处理+递归调用,也就是说一次递归调用从整体上看是实现函数功能,从局部上看就是处理一次子问题
上例题
例 1:给出一个整数num,反复将num各个位上的数字相加,直到结果为一位数
- 函数功能:累加各位数直至结果为一位
- 结束出口: num<10
- 子问题:将num各位上的数字相加
- 递归调用位置:在求出num后调用一次
#递归法
def addDigits(self, num: int) -> int:
#1递归出口,直到结果为一位数即 num<10
if num < 10: return num
#2.处理子问题:反复将num各个位上的数字相加
temp = str(num)
s = 0
for a in temp:
s += int(a)
#3.返回递归调用:没达到需求,继续递归
return self.addDigits(s)
例2:斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。
要求:给定 N,计算 F(N)
- 函数功能:计算 F(N)
- 结束出口: N == 0 or N ==1
- 子问题:计算前俩F(n-1)和F(n-2),这里用整体思想,默认这两个已知
- 递归调用位置:调用2次,即直接返回
F(n-1)+F(n-2)
class Solution:
def fib(self, N: int) -> int:
#结束出口
if N == 1 : return 1
if N == 0 : return 0
#处理子问题(前两者相加)+递归调用
return self.fib(N-1)+self.fib(N-2)
例3:汉诺塔问题
解决思路:
- 首先将最上面的n-1个盘子从A移到B柱子
- 然后将最下面的一个盘子从A移到C柱子
- 最后将n-1个盘子从B移到C柱子
注意:递归函数需要处理的子问题是:将最底部的元素移到盘子C中
#主函数
def hanota(self, A, B, C):
'''
A: List[int] 初始数据列表
B: List[int] 辅助移动数据列表
C: List[int] 最终移动数据列表
return: None
'''
n = len(A)
self.move(n, A, B, C)
# 定义move函数移动汉诺塔(递归函数)
#子问题:将A盘子最底部元素借助盘子B移动到盘子C
def move(self,n, A, B, C):
#结束条件
if n == 1:
C.append(A.pop()) #移动最底的元素到C
return
#递归调用+处理子问题(将A的最后一个移到C)
self.move(n-1, A, C, B) # 将A上面n-1个通过C移到B
C.append(A.pop()) # 将A最后一个移到C
self.move(n-1,B, A, C) # 将B上面n-1个通过空的A移到C