火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

第六课 递归入门

作者: 作者的头像   huolong , 时间:2023-01-12 19:41:03 , 所有人可见, 阅读  19

斐波那契数列

斐波那契数列通项公式

$F(n) = \frac{\sqrt{5}}{5}((\frac{1+\sqrt{5}}{2})^n - (\frac{1-\sqrt{5}}{2})^n)$

斐波那契数列时间复杂度

递归缺点

大量的重复计算

递归改进

  • 递归转递推
  • 递归转记忆化递归(带备忘录的递归)

时间复杂度

递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归中的操作次数。

举例:求$x$的$n$次方 想一下这么简单的一道题目,代码应该如何写呢。 最直观的方式应该就是,一个for循环求出结果,代码如下:

int function1(int x, int n) {
    int result = 1;  // 注意 任何数的0次方等于1
    for (int i = 0; i < n; i++) {
        result = result * x;
    }
    return result;
}

时间复杂度为$O(n)$,有没有效率更好的算法呢。

那么就可以写出了如下这样的一个递归的算法,使用递归解决了这个问题。

int function(int x, int n) {
    if (n == 0) {
        return 1; 
    }
    return function(x, n - 1) * x;
}

那么这个代码的时间复杂度是多少?

一些同学可能一看到递归就想到了O(logn),其实并不是这样,递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归中的操作次数。

那再来看代码,这里递归了几次呢?

每次$n-1$,递归了$n$次时间复杂度是$O(n)$,每次进行了一个乘法操作,乘法操作的时间复杂度一个常数项$O(1)$,所以这份代码的时间复杂度是 $n * 1 = O(n)$。

这个时间复杂度还是太高,于是又写出了如下的递归算法的代码:

int function(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function(x, n / 2) * function(x, n / 2) * x;
    }
    return function(x, n / 2) * function(x, n / 2);
}

这份代码的时间复杂度又是多少呢?

我们来分析一下,首先看递归了多少次呢,可以把递归抽象出一颗满二叉树。刚刚同学写的这个算法,可以用一颗满二叉树来表示(为了方便表示,选择$n$为偶数$16$),如图:

当前这颗二叉树就是求$x$的$n$次方,$n$为$16$的情况,$n$为$16$的时候,进行了多少次乘法运算呢?

这棵树上每一个节点就代表着一次递归并进行了一次相乘操作,所以进行了多少次递归的话,就是看这棵树上有多少个节点。

熟悉二叉树话应该知道如何求满二叉树节点数量,这颗满二叉树的节点数量就是 $2^3 + 2^2 + 2^1 + 2^0 = 15$,可以发现:这其实是等比数列的求和公式,这个结论在二叉树相关的面试题里也经常出现。

这么如果是求$x$的$n$次方,这个递归树有多少个节点呢,如下图所示:($m$为深度,从$0$开始)

时间复杂度忽略掉常数项$-1$之后,这个递归算法的时间复杂度依然是$O(n)$。对,你没看错,依然是$O(n)$的时间复杂度!

这个递归的算法依然还是$O(n)$啊。

那么$O(logn)$的递归算法应该怎么写呢?

想一想刚刚给出的那份递归算法的代码,是不是有哪里比较冗余呢,其实有重复计算的部分,于是又写出如下递归算法的代码:

int function(int x, int n) {
    if (n == 0) {
        return 1;
    }
    int t = function(x, n / 2);
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

再来看一下现在这份代码时间复杂度是多少呢?

依然还是看他递归了多少次,可以看到这里仅仅有一个递归调用,且每次都是 $n/2$ ,所以这里我们一共调用了$log$以$2$为底$n$的对数次。

每次递归了做都是一次乘法操作,这也是一个常数项的操作,那么这个递归算法的时间复杂度才是真正的$O(logn)$。

二叉树性质

二叉树性质

二叉树的五种遍历

前序遍历:根-左-右 中序遍历:左-根-右 后序遍历:左-右-根

推导 前序 + 中序 => 后续 后续 + 中序 => 前序 前序 + 后续 不唯一解

一:二叉树的前序遍历:根-左-右 先遍历根节点,然后 先遍历完 左子树,最后遍历右子树;在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树 前序遍历 = 深搜 前序遍历结果:ABDECF(第一个是根节点)

二:二叉树的中序遍历:左-根-右 先遍历完左子树,然后遍历根节点,最后遍历右子树;在遍历左、右子树时,仍然先遍历左子树,然后遍历根节点,最后遍历右子树; 中序遍历结果:DBEAFC

三:二叉树的后序遍历:左-右-根 先遍历完左子树,然后再遍历完右子树,最后遍历根节点;在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根节点 后序遍历结果:DEBFCA(最后一个一定是根节点)

四:二叉树的深度优先搜索 从 root 开始,一条路走到底,然后再退回到上一个节点,然后再走下一条路,直到所有节点都遍历完; 遍历顺序如下:

五:二叉树的宽度优先遍历(层次遍历) 从 root 开始遍历,先遍历这个节点的相邻节点,然后再依次遍历相邻节点的相邻节点,也就是层序遍历 宽搜的顺序与序号相同

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

©2026加盟我们 | 关于我们 | ACM课程 | 常见问题 | 成果墙 | 评测记录 | 浙ICP备2021013995号
在线画图 | OI WIki | 打字练习
火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码