算法设计优化——Fibonacci数

本文探讨了使用二分递归、线性递归和迭代方法求解Fibonacci数列的算法,重点分析了它们的时间复杂度(分别为O(2^n),O(n),O(n)),空间复杂度(分别为O(2^n),O(n),O(1)),并提出了优化策略以减少重复计算。
摘要由CSDN通过智能技术生成

0. 前言

介绍下求解Fibonacci数算法的优缺点。
数列递归形式:
在这里插入图片描述

1. 二分递归版 O( 2 n 2^n 2n)

1.1 代码与问题

__int64 fib ( int n ) { //计算Fibonacci数列的第n项(二分递归版):O(2^n)
   return ( 2 > n ) ?
          ( __int64 ) n //若到达递归基,直接取值
          : fib ( n - 1 ) + fib ( n - 2 ); //否则,递归计算前两项,其和即为正解
}

该算法需要运行O( 2 n 2^n 2n)时间才能计算出第n个Fibonacci数。这一指数复杂度的算法,在实际环境中毫无价值。
在这里插入图片描述
CPU资源未被占满,进程在满负荷运行,但是依然运行地非常非常慢。

1.2 复杂度分析

在这里插入图片描述
上述推导看复杂度为指数( 2 n 2^n 2n
估算下
ϕ 43 \phi^{43} ϕ43 = 2 30 2^{30} 230 = 1 0 9 10^{9} 109flo = 1 sec(计算工作量大概可以用10地9次方条基本操作指令来度量,10的9次方就是现在主流计算机主频的cpu在1s中大致能够吞吐的计算量,亦是fib(43)计算大概需要1s)

1.3 递归分析

使用第二种手段对上述代码进行分析
在这里插入图片描述
究其原因在于,计算过程中所出现的递归实例的重复度极高!!!

1.4 优化策略

借助一定量的辅助空间,在各子问题求解之后,及时记录下其对应的解答,使得每个递归实例从原理上来讲只需要计算一次。

2. 线性递归 O(n)

2.1 代码示例

将Fibonacci数的各项所计算的结果制作成一个表格,将每项计算结果存入表中,每次需要递归实例的时候便可以查表获取。智能屏蔽此次和后续调用。在O(1)时间内返回结果。

__int64 fib ( int n, __int64& prev ) { //计算Fibonacci数列第n项(线性递归版):入口形式fib(n, prev)
   if ( 0 == n ) //若到达递归基,则
      { prev = 1; return 0; } //直接取值:fib(-1) = 1, fib(0) = 0
   else { //否则
      __int64 prevPrev; prev = fib ( n - 1, prevPrev ); //递归计算前两项
      return prevPrev + prev; //其和即为正解
   }
} //用辅助变量记录前一项,返回数列的当前项,O(n)

2.2 时间复杂度和空间复杂度

该算法呈线性递归模式,递归的深度线性正比于输入n,前后共计仅出现O(n)个递归实例,累计耗时不超过O(n)。遗憾的是,该算法共需使用O(n)规模的附加空间。

3. 迭代 O(n)

在这里插入图片描述

3.1 代码示例

计算方向为自小而大,自底而上,这样由原来的递归算法改进为迭代型算法,同样可以完成刚才工作。如果比作上楼梯,那么在每个台阶上只需停留一次。

例如fib(5):
f(5) = f(4) + f(3)
f(4) = f(3) + f(2)
f(3) = f(2) + f(1)
f(2) = f(1) + f(0)
f(1) = f(0) + f(-1)
迭代的计算是自底而上,所以是算f(1) f(2) f(3)f(4) f(5)
g = g+f 等同于 f(1) = f(0) + f(-1) 再迭代
g = g+f 等同于 f(2) = f(1) + f(0) ,
这次f(0)是未知的,所以f = g-f就是算出下次迭代的末项f(0)
f(0) = f(1) - f(-1) 等同于 f = g-f

__int64 fibI( int n ) { //计算Fibonacci数列的第n项(迭代版):O(n)
   __int64 f = 1, g = 0; //初始化:fib(-1)、fib(0)
   while ( 0 < n-- ) {
      g += f;
      f = g - f;
   } //依据原始定义,通过n次加法和减法计算fib(n)
   return g; //返回
}

3.2 时间复杂度和空间复杂度

这里仅使用了两个中间变量f和g,记录当前的一对相邻Fibonacci数。整个算法仅需线性步
的迭代,时间复杂度为O(n)。更重要的是,该版本仅需常数规模的附加空间,空间效率也有了极大提高。可以认为空间复杂度是个常数O(1)。

4. 封装对象

迭代版 fibI()算法,实现支持如下接口的 Fib 类。

class Fib { //Fibonacci数列类
private:
   Rank f, g; //f = fib(k - 1), g = fib(k)。均为int型,很快就会数值溢出
public:
   Fib ( Rank n ) //初始化为不小于n的最小Fibonacci项
   { f = 1; g = 0; while ( g < n ) next(); } //fib(-1), fib(0),O(log_phi(n))时间
   Rank get()  { return g; } //获取当前Fibonacci项,O(1)时间
   Rank next() { g += f; f = g - f; return g; } //转至下一Fibonacci项,O(1)时间
   Rank prev() { f = g - f; g -= f; return g; } //转至上一Fibonacci项,O(1)时间
};

为定位至不小于n的最小项,以下反复调用next()接口依次递增地遍历,直至首次达到或者超过n。因n与Fibonacci数的第 l o g ϕ log_\phi logϕ(n)项渐进地同阶,故整个遍历过程不超过 l o g ϕ log_\phi logϕ(n)步。

next()接口对f和g的更新方式,与fibI()算法完全一致。
取前一项的方法亦与此相似,只不过方向颠倒而已。

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值