通俗讲:数据结构递归思想

通俗讲:数据结构递归思想

脑容量有限,拒绝花里胡哨

一个递归求阶乘的例子

#如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会终止当前递归函数(即结束出口)

记住以上黑体字,你就能解决大部分的递归问题了

递归递归,先递后归

先递后归

递归的优点和缺点

优点

  • 代码简洁

  • 易于理解,如在树遍历中,递归的实现明显比循环简单。


缺点

  • 每次调用都需要在内存栈中分配空间以保存参数,返回值和临时变量,降低了效率
  • 递归的分解的多个小问题存在重叠的部分,即存在重复计算
  • 栈空间是有限的,当调用的次数太多,可能会超出栈的容量,造成调用栈溢出

综合各方面,迭代的效率更佳,但有些问题用迭代处理更复杂

用迭代思想去思考递归

  1. 循环结束条件 -> 递归结束出口
  2. 循环体-> 处理相同解决方案的子问题
  3. 进行下一次循环->返回调用递归函数

解题心得

  • 一定不要试图跟踪大型递归的过程,否则会你让头脑炸裂,甚至怀疑人生
  • 在调用递归的时候利用整体思想,默认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:汉诺塔问题

leetcode 08.06

解决思路:

  • 首先将最上面的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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值