C++基础题_小青蛙跳台阶递归与非递归

1. 问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳 n 级的台阶总共有多少种跳法。

2. 问题分析

问题分析

  1. 递推关系分析
    当它最后要跳到 n 级台阶时,有两种方式:
    第一种:从 n-1 级台阶跳 1 步到达第 n 级,前面 n-1 级台阶的跳法有 f(n-1) 种
    第二种:从 n-2 级台阶跳 2 步到达第 n 级, 前面 n-2 级台阶的跳法有 f(n-2) 种
    因此,青蛙跳到第 n 级台阶的跳法为两种跳法数量之和:f(n) = f(n-1) + f(n-2)
    —— 斐波那契数列问题
  2. 基准条件分析
    当 n == 1,只有一种跳法
    当 n == 2,有两种,跳 1 次 2 级,或跳 2 次 1 级
    f(1) = 1 , f(2)=2

3. 递归

递归方式直观,但但存在大量的重复计算,如果n较大,计算时间会迅速增加

int jumpWays(int n)
{
    //基准条件
    if (n == 1) return 1;
    if (n == 2) return 2;

    //递归调用
    return jumpWays(n - 1) + jumpWays(n - 2);
}

4. 非递归

记录已经计算过的结果来避免重复计算
用两个变量 pre1 和 pre2 分别保存跳到第 n-1 级和 n-2 级台阶的跳法数,每次计算时更新这两个变量

int jumpWaysOptimized(int n)
{
    if (n == 1) return 1;
    if (n == 2) return 2;

    //记录前两个跳法
    int pre1 = 1;
    int pre2 = 2;
    int current=0;

    //从第3级开始计算跳法
    for (int i = 3; i <= n; i++)
    {
        current = pre1 + pre2;
        pre1 = pre2;
        pre2 = current;
    }
    return current; 
}

5. 对比分析

5.1 两种方案的运行时间

在调用时加入计时,需要包含头文件<chrono>,是 C++11 标准库的一个头文件,处理时间相关的功能,源于希腊语“时间”
完整代码如下

#include <iostream>
#include<chrono>
using namespace std;

int jumpWays(int n)  //递归
{
    //基准条件
    if (n == 1) return 1;
    if (n == 2) return 2;

    //递归调用
    return jumpWays(n - 1) + jumpWays(n - 2);
}

int jumpWaysOptimized(int n) //非递归
{
    if (n == 1) return 1;
    if (n == 2) return 2;

    //记录前两个跳法
    int pre1 = 1;
    int pre2 = 2;
    int current=0;

    //从第3级开始计算跳法
    for (int i = 3; i <= n; i++)
    {
        current = pre1 + pre2;
        pre1 = pre2;
        pre2 = current;
    }

    return current; 
}

int main() 
{
    int n = 40;
   //开始计时
    auto start = chrono::high_resolution_clock::now();

    //递归
    cout << "跳法有:" << jumpWays(n) << " 种" << endl;    
    auto end1 = chrono::high_resolution_clock::now();

    //非递归
    cout<<"跳法有:"<<jumpWaysOptimized(n)<<" 种"<<endl;
    auto end2 = chrono::high_resolution_clock::now();

    //运行时间
    chrono::duration<double> dura1 = end1 - start;
    chrono::duration<double> dura2 = end2 - end1;
    cout << "递归用时:" << dura1.count() << " 秒\n";
    cout<<"非递归用时:" << dura2.count() << " 秒\n";

    return 0;
}

某次运行结果为:
跳法有:165580141 种
跳法有:165580141 种
递归用时:0.807355 秒
非递归用时:0.0007137 秒

5.2 复杂度分析

5.2.1 递归方案

在递归写法中,函数 jumpWays(n) 会调用 jumpWays(n-1) 和 jumpWays(n-2),每次的递归调用会不断地分裂出新的递归调用。
假设用一棵递归树来表示递归调用的过程:

  • 根节点是 jumpWays(n)
  • 根节点有两个子节点,分别是 jumpWays(n-1) 和 jumpWays(n-2)
  • 每个节点会再递归地调用两个子问题

假设我们要计算 f(5):

                f(5)
              /      \
          f(4)        f(3)
         /    \      /    \
      f(3)    f(2) f(2)   f(1)
     /   \    /   \
  f(2)  f(1) f(1) f(0)
  /  \
f(1) f(0)

f(n) 的递归结构是 f(n-1) + f(n-2),递归树中会有许多重复计算,比如 f(2) 和 f(1) 被多次调用
递归树的高度,也就是从根节点到叶子节点的最大深度,直接反映了递归调用的层次。在这个问题中,递归树的高度等于 n
递归树的节点总数表示了递归调用的总次数,这也是我们分析时间复杂度的关键点。每个节点对应一次函数调用

时间复杂度

递归调用的次数并不仅仅是 n 次。由于每个节点会递归调用两个子问题,并且子问题之间会产生重叠计算,当 n 很大时,递归树的节点数量几乎成倍增长。每个递归调用会生成两个新的子调用,在最坏情况下递归调用次数接近 2^n,指数级别增长,接近于斐波那契数的大小,因此,递归的时间复杂度是 O(2^n),这是一种指数级复杂度。对于较大的 n 值,递归方法的时间复杂度非常高。

空间复杂度

空间复杂度由递归调用的栈深度决定。每次递归调用都会在调用栈中创建一个新的栈帧,当递归深度为 n 时,最大栈深度就是 n,空间复杂度是 O(n)

5.2.2 非递归方案

也是动态规划思路的体现
时间复杂度 O(n)
只需从第3级台阶到第n级台阶遍历计算一次
空间复杂度 O(1)
只使用了常数级别的额外空间

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dotdotyy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值