14天阅读挑战赛

本篇是学习了《趣学算法(第2版)》 第一章之后总结的。

上一篇讲到了等比数列求和问题,求Sn=1+2+22+23+...+263=?S_n = 1 + 2 + 2^2 + 2^3 + ... + 2^{63}= ?Sn​=1+2+22+23+...+263=?,该函数属于爆炸增量函数,如果采用常规运算,则要考虑算法的时间复杂度。

算法时间复杂度

常见的算法时间复杂度有以下几类。

  1. 常数阶。
    常数阶算法的运行次数是一个常数,如5、20、100。常数阶算法的时间复杂度通常用O(1)表示。

  2. 多项式阶。
    很多算法的时间复杂度是多项式,通常用 0(n)、O(n2)O(n^2)O(n2)、0(n3)0(n^3)0(n3)等表示。

  3. 指数阶。
    指数阶算法的运行效率极差,程序员往往像躲“恶魔”一样避开这种算法。指数阶算法的时间复杂度通常用O(2n)O(2^n)O(2n)、O(n!)O(n!)O(n!)、O(nn)O(n^n)O(nn)等表示。

  4. 对数阶。
    对数阶算法的运行效率较高,通常用O(logn)O(logn)O(logn)、O(nlogn)O(nlogn)O(nlogn)等表示。
    指数阶增量随着的增加而急剧增加,而对数阶增长缓慢。它们之间的关系如下:

O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)O(n!)<O(nn)O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)O(n!)<O(n^n)O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)O(n!)<O(nn)

在设计算法时,我们要注意算法复杂度增量的问题,尽量避免爆炸级增量。

算法知识点

  • 斐波那契数

  • 动态规划(拆分子问题;记住过往,减少重复计算)

算法题目

假设第1个月有1对初生的兔子,第2个月进入成熟期,第3个月开始生育兔子,而1对成熟的兔子每月会生
1对兔子,兔子永不死去……那么,由1对初生的兔子开始,12个月后会有多少对兔子呢?

做题思路

这个数列有如下十分明显的特点:从第3个月开始,当月的兔子数=上月兔子数+当月新生兔子数当月的兔子数=上月兔子数+当月新生兔子数当月的兔子数=上月兔子数+当月新生兔子数,而当月新生兔子数=上上月的兔子数当月新生兔子数=上上月的兔子数当月新生兔子数=上上月的兔子数。因此,前面相邻两项之和便构成后一项,换言之:
当月的兔子数=上月兔子数+上上月的兔子数当月的兔子数=上月兔子数+上上月的兔子数当月的兔子数=上月兔子数+上上月的兔子数

斐波那契数如下:

1 ,1 ,2 ,3 ,5 ,8, 13 ,21 ,34 ......

递归表达式

F(n)={1,n=11,n=2F(n−1)+F(n−2),n>2F(n)= \begin{cases} 1&, \text{n=1}\\ 1&, \text{n=2}\\ F(n-1) + F(n-2)&, \text{n>2} \end{cases}F(n)=⎩⎨⎧​11F(n−1)+F(n−2)​,n=1,n=2,n>2​

根据递归表达式,初步的算法代码如下:

const fbn = (n) => {if (n == 1 || n == 2) {return 1} else {return fbn(n-2) + fbn(n-1)}
}

让我们看一下上面算法的时间复杂度,也就是计算的总次数T(n)T(n)T(n)

时间复杂度

时间复杂度算的是最坏情况下的时间复杂度

n=1时,T(n)=1
n=2时,T(n)=1;
n=3时,T(n)=3; //调用Fib1(2)和Fib1(1)并执行一次加法运算(Fib1(2)+Fib1(1))

当n>2时需要分别调用fbn(n-1)fbn(n-2),并执行一次加法运算,换言之:
n>2时,T(n)=T(n−1)+T(n−2)+1;n\gt2时,T(n)=T(n-1)+T(n-2)+1;n>2时,T(n)=T(n−1)+T(n−2)+1;

所以,T(n)>=F(n)T(n) >= F(n)T(n)>=F(n)

问题来了,怎么判断T(n)属于算法时间复杂度的哪种类型呢?

方法一:

画出递归树,每个节点表示计算一次

一棵满二叉树,节点总数就和树的高度指数关系

递归树 F(n)里面存在满二叉树,所以时间复杂度是指数阶的

方法二:

使用公式进行递推

因为时间复杂度算的是最坏情况下的时间复杂度,所以计算第一个括号内的即可

即:T(n)=O(2n)T(n) = O(2^n)T(n)=O(2n),时间复杂度是指数阶

算法改进

降低时间复杂度

不难发现:上面基于递归表达式的算法,存在大量的重复计算,增大了算法的时间复杂度,所以我们可以做出如下改进,以减少时间复杂度

// 利用数组记录过往的值,直接使用,避免重复计算
const fbn2 = (n) => {let arr = new Array(n + 1); // 定义 n + 1 长度的数组arr[1] = 1;arr[2] = 1;for (let i = 3; i <= n; i++) {arr[i] = arr[i - 1] + arr[i - 2]}return arr[n]
}

很显然上面算法的时间复杂度是O(n)O(n)O(n),时间复杂度从指数阶降到了多项式阶。

由于上面算法使用数组记录了所有项的值,所以,算法的空间复杂度变成了O(n)O(n)O(n),我们可以继续改进算法,来降低算法的空间复杂度

降低空间复杂度

采用临时变量,来迭代记录上一步计算出来的值,代码如下:

const fbn3 = (n) => {if (n === 1 || n === 2) {return 1;}let pre1 = 1 // pre1,pre2记录前面两项let pre2 = 1let tmp = ''for (let i = 3; i <= n; i++) {tmp = pre1 + pre2 // 2pre1 = pre2 // 1pre2 = tmp // 2}return pre2
}

使用了三个辅助变量,时间复杂度还是O(n)O(n)O(n),空间复杂度降为O(1)O(1)O(1)

测试算法计算时间

// 斐波那契数列
// 1 ,1 ,2 ,3 ,5 ,8, 13 ,21 ,34 ......const fbn = (n) => {if (n == 1 || n == 2) {return 1} else {return fbn(n-2) + fbn(n-1)}
}
console.time('fbn')
console.log('fbn(40)=', fbn(40))
console.timeEnd('fbn')// 利用数组记录过往的值,直接使用,避免重复计算
const fbn2 = (n) => {let arr = new Array(n + 1); // 定义 n + 1 长度的数组arr[1] = 1;arr[2] = 1;for (let i = 3; i <= n; i++) {arr[i] = arr[i - 1] + arr[i - 2]}return arr[n]
}console.time('fbn2')
console.log('fbn2(40)=', fbn2(40))
console.timeEnd('fbn2')const fbn3 = (n) => {if (n === 1 || n === 2) {return 1;}let pre1 = 1 // pre1,pre2记录前面两项let pre2 = 1let tmp = ''for (let i = 3; i <= n; i++) {tmp = pre1 + pre2 // 2pre1 = pre2 // 1pre2 = tmp // 2}return pre2
}console.time('fbn3')
console.log('fbn3(40)=', fbn3(40))
console.timeEnd('fbn3')

测试结果如下:

fbn(40)= 102334155
fbn: 667.76ms
fbn2(40)= 102334155
fbn2: 0.105ms
fbn3(40)= 102334155
fbn3: 0.072ms

小结

能不能继续降阶,使算法的时间复杂度更低呢?
实质上,斐波那契数列的时间复杂度还可以降到对数阶O(logn)O(logn)O(logn),好厉害!!!后面继续探索吧


我是 甜点cc

热爱前端,也喜欢专研各种跟本职工作关系不大的技术,技术、产品兴趣广泛且浓厚,等待着一个创业机会。本号主要致力于分享个人经验总结,希望可以给一小部分人一些微小帮助。

希望能和大家一起努力营造一个良好的学习氛围,为了个人和家庭、为了我国的互联网物联网技术、数字化转型、数字经济发展做一点点贡献。数风流人物还看中国、看今朝、看你我。

算法 | 详解斐波那契数列问题相关推荐

  1. 三种方法详解斐波那契数列

    本文同步.md文件,在展示上会有些许问题 原始版本在语雀平台.请各位移步查阅. 代码保存在码云平台供有需要者下载阅览. 本文采用暴力递归,带备忘录的递归,以及动态规划求解斐波那契数列.不到之处,欢迎指 ...

  2. 算法之矩阵计算斐波那契数列

    算法之矩阵计算斐波那契数列 本节内容 斐波那契介绍 普通方式求解斐波那契 矩阵概念 矩阵求幂 矩阵求解斐波那契 1.斐波那契介绍 斐波那契数列有关十分明显的特点,那是:前面相邻两项之和,构成了后一项. ...

  3. 【js算法】js斐波那契数列的多种算法

    斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为"兔子数列" ...

  4. 【使用递归玩通关汉诺塔游戏】算法01-递归(斐波那契数列、汉罗塔问题)-java实现

    递归 定义:在一个方法(函数)的内部调用该方法(函数)本身的编程方式 简而言之就是 "自己调自己" 在玩游戏之前让我们先对递归有一个简单的了解吧! 5.1 递归简介 递归必须有一个 ...

  5. 【算法编程】斐波那契数列

    题目来源:牛客网剑指offer 题目描述:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项.n<=39 C++:5ms 476k #include <iostr ...

  6. 递归、尾递归、迭代算法【在 斐波拉契数列】上的实现

    /*     递归: [逆序]从未知点推到已知点,[顺序]代入已知点结果,从已知点带入并计算到未知点,最终到终点     尾递归: 从起点开始,依顺序计算结果,并无限靠近最终目标点     迭代: 从 ...

  7. 算法—递归生成斐波那契数列

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.递归生成斐波那契数列 二.使用步骤 1.伪代码 2.c 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如 ...

  8. 数据结构与算法--再谈递归与循环(斐波那契数列)

    再谈递归与循环 在某些算法中,可能需要重复计算相同的问题,通常我们可以选择用递归或者循环两种方法.递归是一个函数内部的调用这个函数自身.循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算. ...

  9. 算法题003 斐波那契(Fibonacci)数列

    斐波那契(Fibonacci)数列 题目来源 斐波那契(Fibonacci)数列是经典的递推关系式定义的数列. 第一项是0,第二项是1,之后的每一项都是前面两项之和. POJ3070:http://p ...

最新文章

  1. 第五周周记(国庆第五天)
  2. PHP相关关系及定义
  3. linux进程及作业管理实验,Linux 进程及作业管理(示例代码)
  4. centos6.5安装jira6.3.6详细文档汉化破解
  5. windows查看进程线程的命令pslist
  6. 漫画:什么是单例设计模式
  7. linux查看openjdk的安装的路径
  8. Python爬虫实战(一):爬糗事百科段子
  9. python所有软件-如何在Python中列出所有已安装的软件包及其版本?
  10. html入门难,HTML+CSS入门之打造全网最劲富文本系列之大话技术难点与特色设计
  11. 股市一跌再跌,是在提醒我们什么?
  12. php遇到Allowed memory size of 134217728 bytes exhausted错误解决方法
  13. c语言 ZZ转字符串,C语言 字符串中的转义字符与字符串的长度 zz
  14. exe4j将jar包转成exe文件
  15. 自己计算机的网络密码,怎么知道自己宽带上网的用户名密码,我打开电脑就能直接上网。...
  16. LeetCode刷题-种花问题
  17. mysql 原子操作
  18. 节点表征学习[GCN、GAT]
  19. JAVA面试题大全(含答案)
  20. node 介绍、安装、升级(node npm)

热门文章

  1. java unpack_参数,解包-UNpack
  2. html象棋开题报告设计要求,C++游戏设计中国象棋开题报告.docx
  3. 哥特巴赫猜想 尾递归 湘潭孕妇之后的自我检讨
  4. Python oauth2登录Outlook读取邮件
  5. 改善编程体验: IdeaVimExtension介绍
  6. (七)Java垃圾收集器详解
  7. 图像成像原理与相机标定
  8. MBA-day20 不定方程问题-练习题
  9. 双路由器设置经验:无线路由器接房东的网线,房东给予IP和DNS
  10. 数据分析师要掌握SQL到什么程度?