我们都知道,数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间。所以,执行效率是算法一个非常重要的考量指标。那如何来衡量你编写的算法代码的执行效率呢?这里就要用到时间、空间复杂度分析。

1、什么是复杂度分析?

复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。常见的复杂度并不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。

2、为什么需要进行复杂度分析?

最主要的原因就是事后统计法有非常大的局限性,主要表现在

  • 测试结果非常依赖测试环境

测试环境中硬件的不同会对测试结果有很大的影响。比如,我们拿同样一段代码,分别用 Intel Core i7 处理器和 Intel Core i3 处理器来运行,不用说,i7 处理器要比 i3 处理器执行的速度快很多。还有,比如原本在这台机器上 a 代码执行的速度比 b 代码要快,等我们换到另一台机器上时,可能会有截然相反的结果。

  • 测试结果受数据规模的影响很大

对同一个排序算法,待排序数据的有序度不一样,排序的执行时间就会有很大的差别。极端情况下,如果数据已经是有序的,那排序算法不需要做任何操作,执行时间就会非常短。除此之外,如果测试数据规模太小,测试结果可能无法真实地反映算法的性能。比如,对于小规模的数据排序,插入排序可能反倒会比快速排序要快!

PS:我们把代码跑一遍,通过统计、监控,就能得到算法执行的时间和占用的内存大小这种方式称为事后统计法。

3、时间复杂度详解

时间复杂度又叫做渐进式时间复杂度。我们通常通过大 O 复杂度表示法来表示时间复杂度。在使用大O表示法时,我们可以忽略其低阶、常量、系数,则只需要关注循环执行次数最多的一段代码

时间复杂度可以分为7种类型,分别是:

  • O(1): 常数复杂度(常数阶)

  • O(log n): 对数复杂度(对数阶)

  • O(n): 线性时间复杂度(线性阶)

  • O(n log n):线性对数复杂度(线性对数阶)

  • O(n^2):平方、O(n^3): 立方、O(n^k): k次方(k次方阶)

  • O(2^n): 指数(指数阶)

  • O(n!): 阶乘(阶乘阶)

其中O(1)、O(log n)、O(n)、O(n^2)、O(n log n)、O(n^2)为多项式阶级;O(2^n)、O(n!)为非多项式阶级。当数据规模 n 越来越大时,非多项式量级算法的执行时间会急剧增加,求解问题的执行时间会无限增长。所以,非多项式时间复杂度的算法其实是非常低效的算法。因此,我们主要来看几种常见的多项式时间复杂度。

3.1、O(1) 常数阶

只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。

 int i = 8;int j = 6;int sum = i + j;

3.2、O(log n) 对数阶

指的是代码的执行次数成对数方式递增,如下代码

i=1;while (i <= n)  {i = i * 2;}

从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当大于 n 时,循环结束。实际上,变量 i 的取值就是一个等比数列。所以,这段代码的时间复杂度就是 O(log2n),即为O(log n)。

3.3、O(n) 线性阶

指的是代码的执行次数成线性递增,如下代码

int cal(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}

由代码我们可以看出在代码中2、3行只会执行一次,则他们的时间复杂度就是O(1),第4、5行执行n次,即时间复杂度为O(2n),总的时间复杂度为O(1+2n),由于在计算时间复杂度时只关注循环次数最多的代码,并使用大O表示法时可以忽略其低阶、常量、系数,则最终的时间复杂度为O(n)。

3.4、O(n log n) 线性对数阶

如果一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是 O(nlogn) 了。而且,O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)

for (int i = 0; i < n; i++) {i=1;while (i <= n)  {i = i * 2;}
}

3.5 O(n^k) 次方阶 如n^2 平方、n^3 立方

void cal(int n) {int sum_3 = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1; for (; j <= n; ++j) {sum_3 = sum_3 +  i * j;}}}

3.6、分析一段代码时间复杂度的三种方法

3.6.1 只关注循环执行次数最多的一段代码

大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以,我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。

3.6.2 加法法则:总复杂度等于量级最大的那段代码的复杂度

int cal(int n) {int sum_1 = 0;int p = 1;for (; p < 100; ++p) {sum_1 = sum_1 + p;}int sum_2 = 0;int q = 1;for (; q < n; ++q) {sum_2 = sum_2 + q;}int sum_3 = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1; for (; j <= n; ++j) {sum_3 = sum_3 +  i * j;}}return sum_1 + sum_2 + sum_3;}

这个代码分为三部分,分别是求 sum_1、sum_2、sum_3。我们可以分别分析每一部分的时间复杂度,然后把它们放到一块儿,再取一个量级最大的作为整段代码的复杂度。

第一段的时间复杂度是多少呢?这段代码循环执行了 100 次,所以是一个常量的执行时间,跟 n 的规模无关。

这里我要再强调一下,即便这段代码循环 10000 次、100000 次,只要是一个已知的数,跟 n 无关,照样也是常量级的执行时间。当 n 无限大的时候,就可以忽略。尽管对代码的执行时间会有很大影响,但是回到时间复杂度的概念来说,它表示的是一个算法执行效率与数据规模增长的变化趋势,所以不管常量的执行时间多大,我们都可以忽略掉。因为它本身对增长趋势并没有影响。

那第二段代码和第三段代码的时间复杂度是多少呢?答案是 O(n) 和 O(n2)。

综合这三段代码的时间复杂度,我们取其中最大的量级。所以,整段代码的时间复杂度就为 O(n2)。也就是说:总的时间复杂度就等于量级最大的那段代码的时间复杂度

4、空间复杂度

空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。我们常见的空间复杂度就是 O(1)、O(n)、O(n^2),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。

具体的例子如下

void print(int n) {int i = 0;int[] a = new int[n];for (i; i <n; ++i) {a[i] = i * i;}for (i = n-1; i >= 0; --i) {System.out.println(a[i]);}
}

跟时间复杂度分析一样,我们可以看到,第 2 行代码中,我们申请了一个空间存储变量 i,但是它是常量阶的,跟数据规模 n 没有关系,所以我们可以忽略。第 3 行申请了一个大小为 n 的 int 类型数组,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。

5、为什么说性能测试和时间、空间复杂度不冲突?

由于做一个性能测试测试结果会受到数据规模测试机器等外界因素的影响,所以性能测试不是很具有代表性。而时间复杂度和空间复杂度分析是比较客观的,不会受到外界因素的影响,只有先做了复杂度分析我们才能确实最优方案,最后得到最优解才开始做性能测试。渐进式时间、空间复杂度分析与性能基准测试并不冲突,而是相辅相成的,但是一个低阶的时间复杂度程序有极大的可能性会优于一个高阶的时间复杂度程序,所以在实际编程中,时刻关心理论时间、空间度模型是有助于产出效率高的程序的,同时,因为渐进式时间、空间复杂度分析只是提供一个粗略的分析模型,因此也不会浪费太多时间,重点在于在编程时,要具有这种复杂度分析的思维。

本专栏所有的文章已收录进GitHub中。

地址:https://github.com/17666555910/KeyGenMe.git

01 复杂度分析(上):时间、空间复杂度讲解相关推荐

  1. BFS、DFS复杂度分析(时间、空间)

    一.BFS的复杂度分析 BFS是一种借用队列来存储的过程,分层查找,优先考虑距离出发点近的点.无论是在邻接表还是邻接矩阵中存储,都需要借助一个辅助队列,v个顶点均需入队,最坏的情况下,空间复杂度为O( ...

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

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

  3. 代码复杂度分析——时间、空间复杂度

    最近练习算法题,又看了极客时间中的<数据结构与算法之美>写的真不错,于是总结一下关于复杂度的知识,代码和图片都是课程里面的.虽然是按课程写的,但是自己写一遍最好,否则看过就忘了. 数据结构 ...

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

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

  5. 算法及时间/空间复杂度的分析

    算法(Algorithm) 对特定问题求解步骤的一种描述,是为解决一个或一类问题给出的一个确定的.有限长的操作序列 算法的基本特征 ①输入:有零个或多个输入 ②输出:有一个或多个输出 ③有穷性:在执行 ...

  6. 时间/空间复杂度及常用算法的复杂度比较

    目录 1.什么是复杂度 2.什么是时间/空间复杂度 时间复杂度: 空间复杂度(Space Complexity): 3.常用的排序/算法复杂度比较 我们在做数据库相关的东西时,经常谈到一个概念叫复杂度 ...

  7. 算法设计与分析课程的时间空间复杂度

    算法设计与分析课程的时间空间复杂度: 总结 算法 时间复杂度 空间复杂度 说明 Hanoi $ O(2^n) $ $ O(n) $ 递归使用 会场安排问题 \(O(nlogn)\) \(O(n)\) ...

  8. 03|复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

    目录 为什么需要复杂度分析? 大 O 复杂度表示法 时间复杂度分析 几种常见时间复杂度 空间复杂度分析 为什么需要复杂度分析? 事后统计法:代码跑一遍,通过统计.监控,就能得到算法执行的时间和占用的内 ...

  9. (渐进)复杂度分析(上)

    https://blog.csdn.net/qiaobinXU/article/details/83115072 数据结构和算法本身解决的是"快"和"省"的问题 ...

  10. 简单排序算法时间空间复杂度分析及应用(4)-二分插入排序

    简单排序算法时间空间复杂度分析及应用(4)-二分插入排序 背景: 顾名思义,这个二分插入排序是直接插入排序的进化版,主要变化的地方就是在内循环部分,即外循环的循环节点在确定区域的位置查询方式由原来的直 ...

最新文章

  1. 【初识Java】 -- Java的数据类型与运算符
  2. python圣诞树代码成品图片动态_Python 圣诞树和樱花树源码
  3. JavaEE课程目标、个人目标、互联网应用和企业级应用的区别
  4. 小程序引用其他页面js_来聊聊小程序页面之间如何通信
  5. mysql添加远程登陆权限及mysql远程连接命令
  6. goldilocks数据库_如何找到您的开源Goldilocks区域
  7. asp.net通过webservice调用java接口全过程_100-RPC、RMI、WebService、httpClient、跨域、集群部署...
  8. gpedit msc组策略面板 win10在哪里_Win10家庭版找不到组策略gpedit.msc的解决方法
  9. C++智能指针与动态内存分配
  10. swift 异常捕获try catch的使用
  11. 数据库开发文档记录方法
  12. X264 输出的统计值的含义(X264 Stats Output)
  13. 网络工程师 名词解释
  14. Java加密:一、Base64算法
  15. 直通输出设备 android kodi,PVE直通核显搭建LibreELEC KODI HTPC实现HDMI输出
  16. 深度探索二维码及其应用
  17. BNN-PYNQ安装
  18. 数据库MySQL创库、创表基本命令
  19. 1698无法登录mysql服务器_解决MySql ERROR 1698 (28000) 错误:Access denied for user 'root'@'localhost'...
  20. 程序员的焦虑!程序媛的捉急!测试的前景和钱景知多少?

热门文章

  1. 基于opencv的MTF算法开发
  2. 使用Python实现淘宝订单定时付款
  3. 多元线性方程的python解法
  4. vlan协议中ISL与Dot1Q区别
  5. 无线路由当交换机的设置方法
  6. mysql没有data文件夹
  7. python中运行代码时没有报错但是也没有输出而且还有exit code 0的结束标志
  8. ROS(indigo) 安装和使用更新版本的Gazebo----3,4,5,6,7 附:中国机器人大赛中型组仿真比赛说明
  9. AcWing120 防线
  10. python画图函数