一、复杂度分析

首先要明确一点,数据结构和算法本质是解决“快”和“省”的问题。要描述一个算法的好坏就需要用到复杂度分析了,复杂度分析可分为如下两种。

  • 时间复杂度
  • 空间复杂度

时间复杂度就是描述算法的快,空间复杂度则是描述算法的省。一般说的复杂度都是时间复杂度,毕竟现代计算机存储空间已经不那么拮据了,时间复杂度是我们重点研究的内容。

二、大 O 复杂度表示法

首先看一段代码,求从 1~n 的累加之和。

int demo(int n) {

    int i;
    int sum = 0;

    for(i=1; i<n; i++) {
        sum += i;
    }

    return sum;
}

现在就来估算一下这段代码的执行时间(下面都是以时间复杂度为例讲解,空间复杂度最后再讲)。

CPU 的角度来看,每一行代码都执行着类似的操作读数据-运算-写数据。这里为了方便计算,假设每行代码的执行时间都是一样的,用 t 表示执行一行代码所需要的时间,n 表示数据规模的大小,T(n) 表示代码执行的总时间。

那么这段代码总执行时间是多少呢?我们来数一下。

首先,函数体内有 5 条语句,第 1、2、5 条语句总共执行了 3 次,所需时间是 3*t;第 3、4 条语句各自执行了 n 次,所需时间是 2*n*t。把这两个代码段执行的时间相加,所得到的结果就是这段代码总共所需的时间。

深度学习模型算法复杂度分析 算法复杂度的分析方法_数据结构

通过上述公式可以得到一个规律,T(n) 随着 n 变大而变大,变小而变小。所以,T(n)n 是成正比的,用数学符号表示就可以写成。

深度学习模型算法复杂度分析 算法复杂度的分析方法_C_02

其中 f(n) 是代码段执行所需的时间之和,O 表示 T(n)f(n) 之间的关系是成正比的。

由公式可得代码段执行所需的时间可表示为 深度学习模型算法复杂度分析 算法复杂度的分析方法_数据结构_03。这就是O 时间复杂度表示法。大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随着数据规模增长的变化趋势,所以,也叫做渐进时间复杂度简称时间复杂度

其实深度学习模型算法复杂度分析 算法复杂度的分析方法_数据结构_04并不是最终时间复杂度的表示方式。在实际的复杂度分析中,一般会把公式中的常量系数低阶忽略。因为这三部分并不影响增长趋势(还记得时间复杂度其实是渐进时间复杂度吧!),所以只需要记录一个最大量级就可以了,时间复杂度的最终表示方式就是深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_05

三、复杂度的分析方法

1. 最大量阶
int demo(int n) {

    int i;
    int sum = 0;

    for(i=1; i<n; i++) {
        sum += i;
    }

    return sum;
}

在分析一个算法、一段代码的时间复杂度的时候,只关注循环执行次数最多的那一段代码即可。

2. 加法法则
int demo(int n) {

    int i;
    int sum = 0;

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

    return sum;
}

如果代码中存在着不同量级的时间复杂度,总的时间复杂度就等于量级最大的那段代码的时间复杂度。

3. 乘法法则
int demo(int n) {

    int i;
    int sum = 0;

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

    return sum;
}

如果是嵌套、函数调用、递归等操作,只需要将各部分相乘即可。

四、复杂度的量级

  • 常量阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_06
  • 对数阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_C_07
  • 线性阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_08
  • 线性对数阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_算法_09
  • 平方阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_C_10
  • 立方阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_11
  • k次方阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_12
  • 指数阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_数据结构_13
  • 阶乘阶:深度学习模型算法复杂度分析 算法复杂度的分析方法_算法_14

对于上述不同的量级可以分为两类:多项式量级非多项式量级。其中,非多项式量级只有两个:深度学习模型算法复杂度分析 算法复杂度的分析方法_数据结构_15深度学习模型算法复杂度分析 算法复杂度的分析方法_算法_16,非多项式也叫做 NP问题。

一般情况下,我们常见的复杂度只有深度学习模型算法复杂度分析 算法复杂度的分析方法_C_17深度学习模型算法复杂度分析 算法复杂度的分析方法_时间复杂度_18深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_05深度学习模型算法复杂度分析 算法复杂度的分析方法_算法_20深度学习模型算法复杂度分析 算法复杂度的分析方法_C_21

五、时间复杂度

我们已经分析了时间复杂度,但是还是有一点儿小问题,比如我们要查找某个元素在长度为 n 的数组中的下标。如果按照顺序遍历,最理想的情况是第一个就是我们要找的,所以时间复杂度是 O(1);如果最后一个才找到我们要的数据,那么它的时间复杂度是 O(n)

为了解决同一段代码在不同情况下时间复杂度出现量级差异,我们就需要对时间复杂度进一步细化分类,为了更准确、更全面的描述代码的时间复杂度,引入了一下 4 个概念。

1. 最好情况时间复杂度

代码在最理想情况下执行的时间复杂度。

2. 最坏情况时间复杂度

代码在最坏情况下执行的时间复杂度。

3. 平均情况时间复杂度

上面两个最好、最坏情况都是小概率事件,平均情况时间复杂度才是最能代表一个算法的时间复杂度。因为平均情况时间复杂度需要引入概率进行分析,所以也叫做加权平均时间复杂度

4. 均摊时间复杂度

正常情况下,代码在执行过程中都处于低阶的复杂度,极个别情况会出现高阶的复杂度,这是我们就可以将高阶的复杂度均摊到每个低阶的复杂度上,这种分析使用的是摊还分析法的思想。

其实我们只需要知道时间复杂度就够了。这四种方法都是对时间复杂度的一些特殊情况的补充,也没必要花大力气去研究它,大概知道有这种时间复杂度分类就可以了,如果你自己想学或者有脑残面试官要问这些,那你就自己去查找资料研究研究,这里不会展开讲解。

六、空间复杂度

前面讲解过,时间复杂度是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。那么空间复杂度就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。

看一段代码,定义一个新数组,赋值后遍历输出。

void demo(int n) {

    int i;
    int data[n];

    for(i=0; i<n; i++) {
        data[i] = i * i;
    }

    for(i=0; i<n; i++) {
        printf("%d\n", data[i]);
    }
}

跟时间复杂度分析一样,函数体内第 1 条语句是常量阶,直接忽略;第 2 条语句申请了一个大小为 nint 类型数组,所以整段代码的空间复杂度就是深度学习模型算法复杂度分析 算法复杂度的分析方法_深度学习模型算法复杂度分析_05