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

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

1.1 什么是复杂度分析?

  1. 数据结构和算法解决是 “ 如何让计算机更快时间、更省空间的解决问题 ” 。
  2. 因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能。
  3. 分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度。
  4. 复杂度描述的是算法执行时间(或占用空间)与数据规模的增长关系。

1.2 为什么需要复杂度分析?

  1. 复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半。
  2. 掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本。

1.3如何进行复杂度分析?

1.3.1 事后统计法

通过统计、监控,就能得到算法执行的时间和占用的内存大小。

  • 局限性
    • 测试结果非常依赖测试环境
    • 测试结果受数据规模的影响很大

1.3.2 大 O 复杂度表示法

可以看做是算法代码执行的时间

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

CPU 的角度来看:读数据 - 运算 - 写数据,假设每个代码执行的时间都为unit_time

第 2 、3 行代码分别需要 1 个 unit_time 的执行时间,

第 4 、5 行都运行了 n 遍,所以需要 2n*unit_time 的执行时间,所以这段代码总的执行时间就是 (2n+2)*unit_time 。

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

第 2 、 3 、 4 行代码,每行都需要 1 个 unit_time 的执行时间,第 5 、 6 行代码循环执行了 n 遍,需要 2n * unit_time 的执行时间,第 7 、 8 行代码循环执行了 n 遍,所以需要 2n * unit_time 的执行时间。所以,整段代码总的执行时间 T(n) = (2n +2n+3)*unit_time

可以得出:
所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。
image

  • T(n):代码执行的时间
  • n:表示数据规模的大小
  • f(n):表示每行代码执行的次数总和,是一个公式
  • O:表示代码的执行时间 T(n) 与 f(n) 表达式成正比

大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度( asymptotic time complexity ),简称时间复杂度。

所以第一个例子中的 T(n) = O(2n+2) ,第二个例子中的 T(n) = O(2n +2n+3) 。

但是公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。只需要记录一个最大量级就可以了,如果用大 O 表示法表示刚讲的那两段代码的时间复杂度,就可以记为:T(n) = O(n) ;T(n) = O(n )。

1.4 时间复杂度分析法则

  1. 只关注循环执行次数最多的一段代码。
    大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,所以也只需要关注循环执行次数最多的那一段代码就可以了。
  2. 加法法则:总复杂度等于量级最大的那段代码的复杂度。
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。

1.4.1 几种常见的多项式时间复杂度实例分析

image

  1. 非多项式量级只有两个: O(2 ) 和 O(n!)。非多项式时间复杂度的算法其实是非常低效的算法。通常把时间复杂度为非多项式量级的算法问题叫作 NP ( Non-Deterministic Polynomial ,非确定多项式)问题。
  2. 多项式量级:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,
    O(1) (常数阶)、 O(logn) (对数阶)、 O(n) (线性阶)、 O(nlogn) (线性对数阶)、 O(n^2) (平
    方阶)、 O(n^3) (立方阶)

1.4.1.1 O(1)

只是常量级时间复杂度的一种表示方法

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

它的时间复杂度是 O(1),而不是 O(3)

一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是 Ο(1)。

1.4.1.2 O(logn)和O(nlogn)

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

通过2^x 求解 x 这个问题的时间复杂度就是 O(log2^n)

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

这段代码的时间复杂度为 O(log3^n),

实际上,不管是以多少为底把所有对数阶的时间复杂度都记为 O(logn) (因为对数之间是可以互相转换的,最后提取的常量总是可以忽略)

  • O(nlogn) 其实就是乘法法则循环执行n遍的时间复杂度

1.4.1.3 O(m+n)和O(m*n)

这种情况的代码的复杂度由两个数据的规模来决定

int cal(int m, int n) {
    int sum_1 = 0;
    int i = 1;
    for (; i < m; ++i) {
        sum_1 = sum_1 + i;
    }
    int sum_2 = 0;
    int j = 1;
    for (; j < n; ++j) {
        sum_2 = sum_2 + j;
    }
    return sum_1 + sum_2;
}

无法事先评估 m 和 n 谁的量级大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,上面代码的时间复杂度就是 O(m+n) 。

  • 将加法规则改为: T1(m) + T2(n) = O(f(m) + g(n))
  • 乘法法则继续有效: T1(m)*T2(n) = O(f(m) * f(n))

1.5 空间复杂度分析

表示算法的存储空间与数据规模之间的增长关系

1.6 小结

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

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/ouzhuangzhuang/article/details/87373887