数据结构之复杂度分析(二)

版权声明:小兄弟,慢慢熬吧...... https://blog.csdn.net/u013850277/article/details/90786810

一. 什么是复杂度分析

二. 为什么需要复杂度分析

三. 如何进行复杂度分析

      空间复杂度

四. 复杂度分析的细化


一. 什么是复杂度分析

衡量一段代码的执行效率最主要的无非是“快”、“省”,其中快是指运行速度快,省是指占用空间少。这里所指的复杂度分析指的是时间、空间复杂度。我们常说的以空间换取时间也正是对应这两点。

时间复杂度:代码执行时间随着数据量规模增长的变化趋势,这个时间不是代码的真正执行时间,所以也叫渐近时间复杂度。也可以简单认为时间复杂度就是对一个算法执行所需时间的度量,用大O表示法表示。

空间复杂度:同时间复杂度类似,表示的是渐近空间复杂度,简称空间复杂度,表示的是代码执行所需临时存储空间随着数据量规模增长而形成的变化趋势。也可以简单认为空间复杂度就是对一个算法执行所需临时空间的度量,用大O表示法表示。

二. 为什么需要复杂度分析

数据结构与算法解决的也就是“快”、“省”的问题,因此谈到数据结构就少不了复杂度分析了。

也许你会说直接运行代码进行测试不也就直接得出相关的复杂度了么?首先要确定这种方法在一定程度上是对的。但这种方法有一定的局限性:比如受限于运行环境,运行的数据量。因此我们就需要一个不依赖于运行环境与数据量便可粗略得出程序的运行复杂度的衡量方法,而这个方法正好就是时间、空间复杂度分析。和性能测试相比,复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点。掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本。

三. 如何进行复杂度分析

复杂度分析的推导过程:

下面以一个简单例子来说明时间复杂度

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

因为时间复杂度分析并非指代码实际运行的时间,只是表示代码的执行时间与运行数据量的变化趋势,因此这里可以假设每行代码的执行时间为unit_time。由上述代码可知,第2,3行总代执行了两次,第4,5行总共执行了2n次,那么代码总的执行时间便可表示为(2+2n)* unit_time。这里可以看出所有代码总的执行时间T(n) 与代码执行的次数成正比。

按照这个思路再进行分析如下代码:

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行共需3unit_time, 第5,6行需要2*n* unit_time, 第7,8行为嵌套循环因此所需 2*n*n* unit_time,故该代码所需要时间为 (2n*n + 2n + 3)* unit_time。

尽管我们不知道unit_time的实际值,但是通过上述两段代码的推导过程,我们可以得出一个很重要的规律,也就是所有代码所需要的执行时间T(n)与每行代码所执行的次数成正比。

          这里便引出大O表示法,也是时间复杂度的官方定义。

算法的执行时间与每行代码的执行次数成正比,用T(n) = O(f(n))表示,其中T(n)表示算法执行总时间,f(n)表示每行代码执行总次数,而n往往表示数据的规模。

由于O(n)表示的是代码执行时间与数据量形成的增长趋势,因此对于一个表达式中对整体趋势没有起决定性影响的等项直接忽视掉。一般有如下原则:

          如果运行时间是常数量级的,则统一用O(1) 表示;

          只保留最高阶项,并且省掉最高阶项前面的系数。

          根据上述原则,示例一 T(n)=(2+2n)* unit_time 对应的大O表示法便是 O(n),示例二 T(n)=(2n*n + 2n + 3)* unit_time 便是 O(n*n)。

空间复杂度

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行给 i 额外分配了一个存储空间,该空间为常量阶,因此对于增长趋势的影响可忽略,第3行给数组 a 额外分配了大小为 n 的存储空间,其他基本没有占用太多空间,由于该代码的空间复杂分析为 O(n)。

    常见的复杂度有如下

上图可分为多项式与非多项式阶,其中非多项式阶随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差,也就是常说的NP问题。包括,O(2^n)(指数阶)、O(n!)(阶乘阶)。

四. 复杂度分析的细化

    对于同一段代码在不同情况下时间复杂度会出现量级差异,为了更全面,更准确的描述代码的时间复杂度,所以引入这4个概念。但是在大多数情况下,是不需要区别分析它们的。

  1. 最坏情况时间复杂度:代码在最理想情况下执行的时间复杂度。
  2. 最好情况时间复杂度:代码在最坏情况下执行的时间复杂度。
  3. 平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示。
  4. 均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。

   总的来说:在分析时间复杂度是O(1)还是O(n)的时候最简单就是凭感觉,出现O(1)的次数远大于出现O(n)出现的次数,那么平均平摊时间复杂度就是O(1)。

注:可参考:漫画:什么是时间复杂度?

       该系列博文为笔者学习《数据结构与算法之美》的个人学习笔记小结

猜你喜欢

转载自blog.csdn.net/u013850277/article/details/90786810