10 | 递归:如何用三行代码找到“最终推荐人”?

147 阅读3分钟

如何理解递归

  • 什么场景会用到递归 最终推荐人\

    • 用户 A 推荐用户 B 来注册

    • 用户 B 又推荐了用户 C 来注册\

    • 给定一个用户 ID,如何查找这个用户的“最终推荐人”?\

  • 应用广泛的算法:DFS 深度优先搜索、前中后序二叉树遍历等等\

    • 去的过程叫“递”,回来的过程叫“归”

递归需要满足的三个条件

  • 一个问题可以分解成几个问题:将问题拆分成更小的问题\

  • 分解后的问题除了数据规模不同,求解思路一致:解决问题手段一致\

  • 存在递归终止条件:存在递归终止条件\

如何编写递归代码

  • 关键:写出递推公式(递归问题的关键) ,找到终止条件(基于推导公式做的)

  • 以走台阶为例

    • f(n) = f(n-1)+f(n-2) 划分子问题,由于最后一步必然不同,所以需要相加起来

    • 找出终止条件

      • f(1)=1\

      • f(2)=2\

  • 怎么写递归\

    • 找到如何将大问题分解为小问题的规律,可能是不止一种的小问题

    • 并且基于此写出递推公式\

    • 再推敲终止条件(最后找边界)\

  • 总结\

    • 计算机擅长做重复的事情,所以递归正合它的胃口\

    • 人脑更喜欢平铺直叙的思维方式,不可能想清楚递归的每一步执行\

    • 抽象成递推公式,不去想调用关系,更不要想分解每个步骤

递归代码要警惕堆栈溢出

  • 栈:当函数调用执行完后才出栈,如果递归不结束,调用栈很深的话会有堆栈溢出的风险

  • 避免堆栈溢出

    • 调用深度(是可以确定的深度情况下,10,50)超过一定深度则不向下递归
  • 缺点:

    • 最大允许的递归深度跟当前线程剩余的栈空间大小有关,事先无法计算

递归代码要警惕重复计算

  • 以爬楼梯来看,会重复计算子问题
  • 可以定义散列表来保存已经计算过的结果
  • 时间复杂度为O(n)

\

怎么将递归代码改写为非递归代码?

  • 递归代码的优点:表达力强,写起来简洁
  • 递归代码的缺点:空间复杂度高,有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题
  • 所有的递归都可以改造成非递归方式,但是本质没有改变,还增加了实现的复杂度(手动模拟计算机的入栈和出栈过程)
  • 在链表上地柜时需要注意链表是否成环

总结

  • 递归是一种非常高效、简洁的编码技巧

  • 也存在堆栈溢出、重复计算、函数调用耗时多、空间复杂度高等问题

  • 技巧

    • 拆分成子问题
    • 解决子问题手段一致
    • 存在终止条件