复杂度实例分析

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

虽然代码有很大的不同,但是实际上常见的时间复杂度就只有那么几种,列在下方:
复杂度实例分析_第1张图片对于上方的时间复杂度分类,我们可以粗略地分为两类:多项式量级和非多项式量级。上边列出来的复杂度中,除了O( 2 n 2^n 2n)和O( n ! n! n!)是非多项式量级,其他的都是多项式量级。
时间复杂度为非多项式量级的算法问题叫作 NP(Non-Deterministic Polynomial,非确定多项式)问题。
当数据规模 n 越来越大时,非多项式量级算法的执行时间会急剧增加,求解问题的执行时间会无限增长。所以,非多项式时间复杂度的算法其实是非常低效的算法。因此,关于 NP 时间复杂度这里就不展开讲了。接下来分析几项常见的多项式时间复杂度。
1.O(1)
首先要明确一点,就是O(1)时间复杂度并不是说执行一行代码,而是就算执行多行代码,但是代码执行次数是已知的。举个例子:

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

就像上方的代码这样虽然执行了三行,但是时间复杂度并不是O(3),依然是O(1)。只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度就是O(1)。一般而言,只要代码中不存在循环和递归,即便有上万甚至是更多行代码,时间复杂度依然是O(1)。
2.O( l o g n logn logn)、O( n l o g n nlogn nlogn)
对数时间复杂度很常见,但是也很难分析。例子见下方代码:

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

根据以前文章提到的加法法则:总复杂度等于量级最大的那段代码的复杂度,那么我们只需要计算第三行代码的时间复杂度就可以求出这段代码的时间复杂度。
依据分析可以得出,i的值会是: 2 1 2^1 21 2 2 2^2 22 2 3 2^3 23…… 2 x 2^x 2x
这样的话,就可以得出一个公式 2 x = n 2^x=n 2x=n,经过转化之后,可以得出 x = l o g 2 n x=log_2n x=log2n,所以这段代码的时间复杂度是O( l o g 2 n log_2n log2n)。
若是把代码改成下边这样:

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

时间复杂度依然是O( l o g 2 n log_2n log2n)。用换底公式,可以得出 l o g 3 n = l o g 3 2 ∗ l o g 2 n log_3n= log_32 * log_2n log3n=log32log2n。基于我们前面的一个理论:在采用大O标记复杂度的时候,可以忽略系数,即 O(Cf(n)) = O(f(n))。因为 l o g 3 2 log_32 log32为一个常数,所以O( l o g 3 n log_3n log3n)=O( l o g 3 2 ∗ l o g 2 n log_32 * log_2n log32log2n)=O( l o g 2 n log_2n log2n)。所以,O( l o g 2 n log_2n log2n) 就等于 O( l o g 3 n log_3n log3n)。因此,在对数阶时间复杂度的表示方法里,我们忽略对数的“底”,统一表示为 O(logn)。
如果理解了前面讲的 O(logn),那 O(nlogn) 就很容易理解了。时间复杂度乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。如果一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是 O(nlogn) 了。而且,O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)。
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))
再看一段代码:

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

这段代码中可以乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积继续有效,用公式表达为T1(m)*T2(n) = O(f(m) * f(n)),现在上边这段代码的时间复杂度T(n)=O(mn)。

空间复杂度分析

时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。类比一下,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
还是拿具体的例子来给你说明。(这段代码有点“傻”,一般没人会这么写,现在那这段代码只是为了方便解释。)

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) {
    print out a[i]
  }
}

在第2行申请了一个变量,这只是常量阶空间复杂度,与数据规模n毫无关系,可以忽略。在第3行中,申请了类型为int的数字,大小为n,初次之外没有任何其他额外内存,所以这段代码的空间复杂度为 O(n)。
我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。
我这里借用老师的话来总结一下:复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。常见的复杂度并不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。
复杂度随着数据规模n增长的大致变化趋势:
复杂度实例分析_第2张图片
——极客时间《数据结构与算法之美》学习笔记 Day 6

你可能感兴趣的:(数据结构和算法,算法)