# 0000.为什么需要复杂度分析 | FlyPython - 专业的Python学习社区
0000.为什么需要复杂度分析--LeetCode刷题计划第0篇
Author: flypython
Keywords: 0000.为什么需要复杂度分析,FlyPython - 专业的Python学习社区,flypython, 飞蟒，飞蟒Python，Python入门，Python自动化，Python日报

## 0000.为什么需要复杂度分析

Author：flypython | Date: 2020-02-24 | Category： 飞蟒微课堂 LeetCode

## 写在前面

FlyPython推出的《Python面试专项课程》原计划在春节假期后开始更新，考虑到后面刷题的需求可能增多，我们对题目进行了调整并于今天开始更新。

原先的LeetCode刷题计划中的精选TOP面试题推后，我们先过一遍《程序员面试金典》这本书上的面试题，为基础薄弱的同学夯实基础。

- 网址：https://leetcode-cn.com/problemset/lcci/
- 总题数： 97

欢迎关注我们的公众号flypython和网站flypython.com，我们将持续更新这个专项课程。

## 为什么需要复杂度分析

今天是这个专项课程的第0课，我们带来算法学习最重要的一个部分：复杂度分析。

为什么说复杂度分析是最重要呢？因为数据结构和算法其实解决的是”快”和”省”的问题。”快”就是如何让代码运行得更快，”省”就是让代码节约存储空间。怎么样来衡量你编写的算法代码执行效率呢？这里引出来今天要讲的内容：时间、空间复杂度分析。

你可能会说，不就是执行效率么？我把代码跑一遍，增加cProfile的一些操作，不就得到了执行时间和内存占用么。

其实你上面说的也是一种方法，叫事后统计法，在做性能优化时会大量采用。但是事后统计法有它的局限性。第一，它依赖测试环境，测试环境不一样，得到的数据会都不一样；第二，它受数据规模的影响大，比如排序，排10个数和排100万个数肯定不一样。所以我们不能以具体测试数据来表示，需要的是一个估算的执行效率的方法。这个方法就是我们要讲的大O复杂度表示法。

## 大O复杂度表示法

我们从一个例子开始，下面是求1,2,3,…n的累加和的代码

    1
    2
    3
    4
    5
    6
    7
    8

    1 int cal(int n) {
    2   int sum = 0;
    3   int i = 1;
    4   for (; i <= n; ++i) {
    5     sum = sum + i;
    6   }
    7   return sum;
    8 }

我们假设每一行代码的执行时间是相同的，为unit_time，那么这一段代码，第2，3行执行了一次，4，5行执行了n遍，第7行执行了一次，那总共执行的次数2n+3,执行时间为(2n+3)* unit_time。

可以看出，所有代码的执行时间 T(n) 与每行代码的执行次数成正比。可以用以下公式表示：

其中T(n)为所有代码的执行时间
其中f(n)表示每行代码执行的次数总和

公式中的O，表示代码的执行时间 T(n) 与 f(n) 表达式成正比。我们的例子中，T(n)=O(2n+3)，这就是大O时间复杂度表示法。

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

#### 时间复杂度分析

- O(1): 常数
- O(logn): 对数
- O(n): 线性
- O(nlogn)：线性对数
- O(n^2): 平⽅
- O(n^3): 立⽅
- O(n^k): K次方
- O(2^n): 指数
- O(n!): 阶乘

##### O(1)

    1
    2

    int a=0;
    int b=a;

O(1) 只是常量级时间复杂度的一种表示方法，并不是指只执行了一行代码。只要代码的执行时间不随 n 的增大而增长，这样代码的时间复杂度我们都记作 O(1)。

##### O(log n)

    1
    2
    3

    for(int i=1; i<n; i=i*2){
        printf(i);
    }

对数阶时间复杂度非常常见，同时也是最难分析的一种时间复杂度。从代码中可以看出，变量 i 的值从 1 开始取，每循环一次就乘以 2。当大于 n 时，循环结束。还记得我们高中学过的等比数列吗？实际上，变量 i 的取值就是一个等比数列

通过 2^x = n 求解 x, x=log2n，这段代码的时间复杂度就是 O(log2n)，忽略系数，表示为O(logn)。

##### O(n)

    1
    2
    3

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

for循环代码执行了n遍，因此它消耗的时间是随着n的变化而变化的，所以通过O(n)来表示。如果平行存在一个for循环m遍的，如下:

    1
    2
    3
    4
    5
    6
    7

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

    for(int i=1; i<=m; i++){
        printf(i);
    }

那时间复杂度为O(m+n)。

##### O(nlogn)

    1
    2
    3
    4
    5

    for(int i=1; i<=n; i++){
      for(int j=1; j<=n;j=j*2) {
        printf(i,j);
      }
    }

线性对数O(nlogn)就是将时间复杂度为O(logn)的代码循环N遍的话，那么它的时间复杂度就是n* O(logn)，也就是了O(nlogn)

##### O(n^2)

    1
    2
    3
    4
    5

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

平方就是O(n)代码再嵌套循环一遍，2层n循环，O(n * n) 为O(n^2)。如果嵌套3层n循环，则为O(n^3)，如果嵌套k层，则为O(n^k)。

如果其中一层循环中的n改为m，如下所示:

    1
    2
    3
    4
    5

    for(int i=1; i<=m; i++){
      for(int j=1; j<=n;j++) {
        printf(i,j);
      }
    }

那它的时间复杂度为O(m * n)

##### O(2^n)和O(n!)

    1
    2
    3

    for(int i=1; i<=math.pow(2,n); i++){
      printf(i);
    }

    1
    2
    3

    for(int i=1; i<=factorial(n); i++){
      printf(i);
    }

以上两个代码例子分别为O(2^n)复杂度和O(n!)复杂度，指数复杂度和阶乘复杂度属于非多项式量级，我们把时间复杂度为非多项式量级的算法问题叫作 NP（Non-Deterministic Polynomial，非确定多项式）问题。

当数据规模 n 越来越大时，非多项式量级算法的执行时间会急剧增加，求解问题的执行时间会无限增长。所以，非多项式时间复杂度的算法其实是非常低效的算法。

具体增长曲线如下图所示：

#### 空间复杂度分析

- O(1)
- O(n)
- O(n²)

##### O(1)

    1
    2
    3

    int i=0;
    int j=1;
    int m = i + j;

代码中的i,j,m不随着处理数据量变化而变化，算法需要的临时空间是固定的，可以表示为空间复杂度O(1)

##### O(n)

    1
    2
    3
    4
    5

    int[] m = new int[n];
    for(i=1; i<=n; ++i)
    {
       printf(i);
    }

这段代码中，第一行new了一个数组出来，这个数据占用的大小为n，这段代码的2-5行，虽然有循环，但没有再分配新的空间，所以这段代码的空间复杂度为O(n)

##### O(n^2)

平方空间复杂度与O(n)类似，new出来的数组如果是n * n的多维数组，那就是O(n^2)复杂度，如果是m * n的多维数组,那就是O(m * n)复杂度。

    1
    2

    int[][] a = new int[n][n];
    int[][] a = new int[m][n];

## 总结

今天我们学习了复杂度分析的相关基础知识，包括了时间复杂度和空间复杂度，主要是用来分析算法执行效率与数据规模之间的增长关系。

常见的复杂度，从低阶到高阶有：O(1)、O(logn)、O(n)、O(nlogn)、O(n^2)，几乎包括了后面学习的所有数据结构和算法的复杂度，希望大家可以好好的掌握。

时间复杂度分析还有最好，最坏，平均，均摊时间复杂度等话题需要好好讨论，请关注后续文章，我们会在刷题过程中穿插讲解。

#### 参考资料

- analysis_of_algorithms: https://en.wikipedia.org/wiki/Master_theorem_(analysis_of_algorithms)