算法是用于解决特定问题的一系列的执行步骤。使用不同算法,解决同一个问题,效率可能相差非常大。为了对算法的好坏进行评价,我们引入 “算法复杂度” 的概念。

1、引例:斐波那契数列(Fibonacci sequence)

已知斐波那契数列: F ( 1 ) = 1 , F ( 2 ) = 1 , F ( n ) = F ( n − 1 ) + F ( n − 2 ) ( n ≥ 3 , n ∈ N ∗ ) F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N^*) F(1)=1,F(2)=1,F(n)=F(n−1)+F(n−2)(n≥3,n∈N∗),求它的通项公式 F ( n ) F(n) F(n)。

求解斐波那契数列的方法有很多种,这里只介绍两种:递归法和平推法。

package com.atangbiji;public class Main {public static void main(String[] args) {// 输出通项F(n)System.out.println(fib1(1));System.out.println(fib1(2));System.out.println(fib1(3));System.out.println(fib1(4));System.out.println(fib1(5));System.out.println(fib2(70));}/** 求斐波那契数列(Fibonacci sequence)* F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)的通项F(n).*//**  方法一:递归法*  最高支持 n = 92 ,否则超出 Long.MAX_VALUE* @param n * @return f(n) * */public static long fib1(int n) {if (n < 1 || n > 92)return 0;if (n < 3)return 1;return fib1(n - 1) + fib1(n - 2);}/**  方法二:平推法*  最高支持 n = 92 ,否则超出 Long.MAX_VALUE* @param n * @return f(n) * */public static long fib2(int n) {if (n < 1 || n > 92)return 0;//n:    1 2 3 4 5 ……//F(n): 1 1 2 3 5 ……long first = 1;long second = 1;for (int i = 3; i <= n; i++) {long sum = first + second;first = second;second = sum;}return second;}}

通过测试,我们可以发现:当n的取值较大时(如:n = 60),若采用递推法计算则会发现迟迟不出结果,若采用平推法计算则可以秒出结果。由此可见, 平推法的效率明显高于递推法。

2、如何评估算法的好坏?

  • 正确性
  • 可读性
  • 健壮性:对不合理输入的反应能力和处理能力。
  • 时间复杂度(time complexity): 估算程序指令的执行次数(执行时间)。
  • 空间复杂度(space complexity): 估算所需占用的存储空间。

注: 一般情况下,我们主要考虑算法的时间复杂度。 (因为目前计算机的内存一般都比较大)

3、时间复杂度的估算

我们可以用程序指令的执行次数来估算时间复杂度。例如:

(1)函数test1

public static void test1(int n) {//总执行次数 = 14// 1(判断语句可以忽略)if (n > 10) {System.out.println("n > 10");} else if (n > 5) {System.out.println("n > 5");} else {System.out.println("n <= 5");}// 1 + 4 + 4 + 4for (int i = 0; i < 4; i++) {System.out.println("test");}
}

(2)函数test2

public static void test2(int n) {//总执行次数 = 1 + 3n//1 + n + n + nfor (int i = 0; i < n; i++) {System.out.println("test");}
}

(3)函数test3

public static void test3(int n) {//总执行次数 = 48n + 1// 1 + 2n + n * (1 + 45)for (int i = 0; i < n; i++) {for (int j = 0; j < 15; j++) { // 1 + 15 + 15 + 15System.out.println("test");}}
}

(4)函数test4

public static void test4(int n) {//总执行次数 = 3n^2 +3n +1// 1 + 2n + n * (1 + 3n)for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) { // 1 + n + n + nSystem.out.println("test");}}
}

(5)★ 函数test5

public static void test5(int n) {//总执行次数 = log2(n)/** n = 2 , 执行 1 次* n = 4 , 执行 2 次* n = 8 , 执行 3 次 * */while ((n = n/2) > 0) { // 倍速减小System.out.println("test"); // 只考虑这一句的执行次数}
}

(6)函数test6

public static void test6(int n) {//总执行次数 = log5(n)while ((n = n/5) > 0) {System.out.println("test"); // 只考虑这一句的执行次数}
}

(7)函数test7

public static void test7(int n) {//总执行次数 = 3n*log2(n) + 3log2(n) + 1// 1 + 2 * log2(n) + log2(n) * (1 + 3n)/** n = 2 , 执行 1 次* n = 4 , 执行 2 次* n = 8 , 执行 3 次 * */for (int i = 1; i < n; i += i) { // i = i + i = i * 2(倍速增大)for (int j = 0; j < n; j++) { // 1 + n + n + nSystem.out.println("test");}}
}

4、大O表示法

为了进一步简化复杂度的计算,我们一般使用大O表示法来描述时间(或空间)复杂度。它表示的是 数据规模为n 时算法所对应的复杂度。

大O表示法的性质:

(1)可以忽略常数、常系数和低阶项。

  • 9 > > O ( 1 ) 9 >> O(1) 9>>O(1)
  • 2 n + 3 > > O ( n ) 2n + 3 >> O(n) 2n+3>>O(n)
  • 3 n 2 + 2 n + 6 > > O ( n 2 ) 3n^2 + 2n + 6 >> O(n^2) 3n2+2n+6>>O(n2)

(2)对数阶一般省略底数,统称 log ⁡ n \log n logn。

  • log ⁡ 2 n = log ⁡ 2 9 ∗ log ⁡ 9 n \log_2 n = \log_2 9*\log_9 n log2​n=log2​9∗log9​n

注: 大O表示法仅仅只是一种粗略的分析模型,是一种估算。 它能帮我们快速了解一个算法的执行效率。

5、常见的复杂度

其中:
O ( 1 ) < O ( log ⁡ n ) < O ( n ) < O ( n log ⁡ n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)<O(\log n)<O(n)<O(n\log n)<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)

  • 当数据规模较小时, 各复杂度对应的曲线如下图所示。

  • 当数据规模较大时, 各复杂度对应的曲线如下图所示。

所以,当数据规模比较大时,复杂度为 O ( n 2 ) O(n^2) O(n2) 我们就很难接受了。

6、斐波那契数算法复杂度分析

(1)递归法

public static long fib1(int n) {if (n < 1 || n > 92)return 0;if (n < 3)return 1;return fib1(n - 1) + fib1(n - 2);
}

假设计算 f i b 1 ( n ) fib1(n) fib1(n)时 f i b 1 ( n − 1 ) fib1(n - 1) fib1(n−1)和 f i b 1 ( n − 2 ) fib1(n - 2) fib1(n−2)的值已经得到,我们可以发现该函数每次执行的时间主要取决于求和运算。因此,该算法函数指令的执行次数等价于该函数被递归调用次数。

当 n = 5 n=5 n=5时,该函数的调用过程如下图所示。

所以,该函数被递归调用的次数 = = = 二叉树的节点数。

即: 2 0 + 2 1 + 2 2 + 2 3 = 2 4 − 1 = 2 n − 1 − 1 = 0.5 ∗ 2 n − 1 2^0+2^1+2^2+2^3=2^4-1=2^{n-1}-1=0.5*2^n-1 20+21+22+23=24−1=2n−1−1=0.5∗2n−1。

因此,该算法的复杂度为 O ( 2 n ) O(2^n) O(2n)。

**注:**细心的同学可能会发现,当 n > 5 n > 5 n>5时,函数被递归调用的次数并不完全等于 0.5 ∗ 2 n − 1 0.5*2^n-1 0.5∗2n−1。

这里需要说明的是: 复杂度是一种估算,我们关心的不是具体的数值,而是量级和趋势。 所以, f i b 1 ( n ) fib1(n) fib1(n)呈指数级增长的趋势是毋庸置疑的。

(2)平推法

public static long fib2(int n) {if (n < 1 || n > 92)return 0;//n:    1 2 3 4 5 ……//F(n): 1 1 2 3 5 ……long first = 1;long second = 1;for (int i = 3; i <= n; i++) {long sum = first + second;first = second;second = sum;}return second;
}

显然,平推法的时间复杂度为 O ( n ) O(n) O(n)。

7、算法的优化方向

(1)用尽量少的执行步骤(运行时间)。

(2)用尽量少的存储空间。

(3)根据情况,空间换时间或者时间换空间。

更多关于复杂度的知识,我们会在后续数据结构和算法的设计与实现过程中穿插讲解。

(本讲完,系列博文持续更新中…… )

参考文献:
[1] 《恋上数据结构与算法》,小码哥MJ
[2] 《数据结构与算法》,邓俊辉

如何获取源代码?

关注 “阿汤笔迹” 微信公众号,在后台回复关键词 “数据结构与算法设计与实现” ,即可获取项目源代码。

原文地址:http://www.atangbiji.com/2020/05/12/complexity/
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。

数据结构与算法——复杂度相关推荐

  1. 如何计算环形复杂度_数据结构与算法复杂度

    数据结构 数据存储于内存时,决定了数据顺序和位置关系的便是数据结构.根据使用数据的目的选择合适的数据结构,可以提高内存的利用率. 链表 链表是数据结构之一,其中的数据呈线性排列.链表在内存中无需存储在 ...

  2. 【数据结构与算法-java实现】二 复杂度分析(下):最好、最坏、平均、均摊时间复杂度的概念

    上一篇文章学习了:如何分析.统计算法的执行效率和资源消耗? 点击链接查看上一篇文章:复杂度分析上 今天的文章学习以下内容: 最好情况时间复杂度 最坏情况时间复杂度 平均情况时间复杂度 均摊时间复杂度 ...

  3. 【数据结构与算法-java实现】一 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

    今天开始学习程序的灵魂:数据结构与算法. 本文是自己学习极客时间专栏-数据结构与算法之美后的笔记总结.如有侵权请联系我删除文章. 我们都知道,数据结构和算法本身解决的是"快"和&q ...

  4. 【数据结构与算法】复杂度分析

    一.什么是复杂度分析? 1.数据结构和算法解决是"如何让计算机更快时间.更省空间的解决问题". 2.因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能. 3.分别用时间复 ...

  5. 数据结构与算法分析c++第四版_数据结构与算法 - 时空复杂度分析

    这周主要总结了时间复杂度的学习,跟小伙伴们分享下,欢迎指正. 一.为何需要分析算法复杂度 挺多同学本科都学习过数据结构和算法这门课,但是有没有想过这门课到底是解决什么问题?科学家设计这些数据结构和算法 ...

  6. 数据结构与算法 (1)复杂度---(时间复杂度)

    这是一个全新的专栏,内容主要是用C语言学习数据结构的初阶内容,一是复习,二是希望能带给大家一些帮助. 从今天开始,我们进入数据结构与算法的学习,在进入今天的内容之前,我们先了解一下什么是数据结构,什么 ...

  7. 数据结构-----算法复杂度(大O表示法)

    为什么要先聊这个算法复杂度呢? 首先,我们先聊聊算法."老衲"经常听别人说起算法,但还是不太明白算法是什么. 先拽一大段概念: 算法(Algorithm)是指解题方案的准确而完整的 ...

  8. 【牛客网】:数据结构——时间复杂度,算法复杂度

    目录 一.时间复杂度 二.算法复杂度 一.时间复杂度  先看一张图: (1)时间频度 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道.但我们不可能也没有必要对每个算法都上机 ...

  9. 数据结构学习笔记:算法复杂度的度量之“大O记号”

    分析算法复杂度的非常重要的方法:大O记号!! 下面来让我们看一下到底什么是大O记号 举个例子: 用一个直尺去评价算法复杂度,上面的刻度就相当于大O记号,我们不一定要一味的强调刻度的精细程度,没有必要. ...

最新文章

  1. 计算机导航辅助教程,计算机导航辅助下微创人工全膝关节置换的初步经验
  2. http php mysql_apache+php+mysql
  3. Composer的简单安装与使用
  4. Linux桌面版横评:三、Fedora 7 Live
  5. BMS开发日记 - One day
  6. vc得到屏幕的当前分辨率方法
  7. About 磁珠(Bead)
  8. 【ArcGIS遇上Python】使用add-in向导开发ArcGIS插件(1):add-in工具介绍及安装
  9. mysql 相同字段相减_mysql datetime 类型字段相减
  10. 这些Windows 10隐藏秘技,你知道几个?
  11. Android笔记 检测网络状态
  12. 帆软删除行操作提示并确认 js:FR.Msg.confirm
  13. txtv28pw河南某中学_有一种寒冷叫不穿秋裤!河南一中学班主任让学生列队挨个检查秋裤...
  14. qq for linux无法安装路径,ubuntu amd 64bit 安装 QQ for linux教程(附 不能使用中文的解决办法)...
  15. java加密算法之JWT篇
  16. Java的高并发编程系列(三)
  17. ubuntu16.04 双显卡 安装N卡驱动
  18. 今日新闻快讯摘要十条
  19. 44岁万达女高管跳楼:摧毁一个中年人有多容易!
  20. 数据结构之栈,栈是很多算法的基础知识,本文带你从0开始了解栈并手写一个栈

热门文章

  1. 上万张零乱照片怎么整理?亲影教你科学地管理图片
  2. 全自动升降柱工作原理分析
  3. 【转】Html Agility Pack ── 一个分析HTML的工具
  4. 红包html页面,JavaScript Html实现移动端红包雨功能页面
  5. XP下软件崩溃,adplus抓取Dump方法
  6. 权限提升:令牌窃取 || 进程注入.
  7. 轻松获取小程序页面路径地址
  8. MySQL:事物ACID特性
  9. python用requests库和xpath爬取站长素材的免费简历模板
  10. 猫狗图片识别(卷积神经网络(CNN)详解)