复杂度分析

63 阅读2分钟

本文基于阅读 极客时间《数据结构与算法之美》 后对复杂度分析相关重要概念的整理记录

1、复杂度分析

数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,更省存储空间。执行效率是算法一个非常重要的考量指标。那如何来衡量你编写的算法代码的执行效率呢?

事后统计法:依赖测试环境,受数据规模影响大,标准难把控。

复杂度分析:不利用测试数据,粗略估算代码的执行效率,指导编码。

2.大 O 复杂度表示法

从 CPU 的角度来看,这段代码的每一行都执行着类似的操作:读数据-运算-写数据.

假设每一次执行所花时间都为都unix_time.总共执行次数为f(n),总共执行时间为T(n),

示例1:

int cal(int n) {

    int sum = 0;

    int i = 1;

    for (; i <= n; ++i) {

    sum = sum + i;

    }

    return sum;

}

示例2:

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;

      }

    }
}

所有代码的执行时间 T(n) 与每行代码的执行次数成正比:

T(n) = O(f(n))

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

3.时间复杂度分析

    大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。

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

  1. 加法法则:总复杂度等于量级最大的那段代码的复杂度
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;
}
  1. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
int cal(int n) {
    int ret = 0;
    int i = 1;
    for (; i < n; ++i) {
     ret = ret + f(i);
    }
 }

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

4.几种常见时间复杂度实例分析

     代码生涯所能接触到的几乎所有复杂度量级:

image.png

常见的复杂度并不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。几乎所有的数据结构和算法的复杂度都跑不出这几个。

image.png

  1. O(1) 示例:
int i = 8;

int j = 6;

int sum = i + j;
  1. O(logn)、O(nlogn)

示例:

i=1;

while (i <= n)  {

i = i * 2;

}

归并排序、快速排序、堆排序的时间复杂度都是 O(nlogn)。

5.空间复杂度分析

空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

void print(int n) {

        int i = 0;

        int[] a = new int[n];

        for (i; i

        a[i] = i * i;

        }

        for (i = n-1; i >= 0; --i) {

        print out a[i]

        }

}

我们常见的空间复杂度就是 O(1)、O(n)、O(n2 )。

6.最好、最坏、平均时间复杂度

// n表示数组array的长度

int find(int[] array, int n, int x) {

    int i = 0;

    int pos = -1;

    for (; i < n; ++i) {

    if (array[i] == x) pos = i;

    }

    return pos;

}

//优化:

// n表示数组array的长度

int find(int[] array, int n, int x) {

    int i = 0;
    
    int pos = -1;

    for (; i < n; ++i) {

        if (array[i] == x) {

        pos = i;

        break;

        }
    }
    return pos;
}

最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。

最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。

最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,发生的概率其实并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均时间复杂度。

要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中不在数组中

我们假设在数组中与不在数组中的概率都为 1/2。另外,要查找的数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1/n。所以,根据概率乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)。

image.png

这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度

用大 O 表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度为O(n)。