复杂度分析(上) 如何分析、统计算的的执行效率和资源消耗?

599 阅读6分钟

学习算法、数据结构,首先看看复杂度相关;

复杂度分析(上) 如何分析、统计算的的执行效率和资源消耗?

什么是复杂度分析

  • 数据结构和算法解决的是 ‘ 如何让计算机更快时间、更省空间’的问题;

  • 从执行时间和占用空间两个维度来分析数据结构 和算法的性能;

  • 分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度;

  • 复杂度描述的是算法执行时间(或占用空间)与数据规模的增长关系;

为什么要分析复杂度?

通过统计、监控,就能得到算法执行的时间和占用的内存大 小;这叫做事后统计法; 有以下缺点;

  • 测试结果非常依赖测试环境: 高端的处理器 速度快于低端的处理器
  • 测试结果受数据规模的影响很大: 高效的排序有时候受数据的的多少影响;

综上:

1.复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点;

2.掌握复杂度分析,在我们开发过程中将能编写出性能更优的代码,有利于提高效率,降低系统开发和维护成本。

大 O 复杂度表示法

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

1 .来源:

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

间,f(n)表示每行代码执行总次数,而n往往表示数据的规模。

2.特点

在计算复杂度的过程中,低阶,常量,系数三部分并不在左右增长趋势,所以可以忽略,我们就记录一个最大量级就可以了;

时间复杂度分析

  1. 只关注循环执行次数最多的一段代码,这段代码执行次数的 n 的量级,就是整段要分析的时间复杂度;
  2. 加法法则:总复杂度等于量级最大的那段代码的复杂度;
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积;

综上:

1.单段代码看高频:比如循环。

2.多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。

3.嵌套代码求乘积:比如递归、多重循环等

4.多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。

几种常见时间复杂度
1.O(1)

O(1)只是常量级时间复杂度的一种表示方法,并不是指只执行了一行代码;比如这段代码,即便有 3 行,它的时间复杂度也是 O(1),而不是 O(3):

1 int i = 8;
2 int j = 6;
3 int sum = i + j;

总结: 只要算法中不存在循环语句,递归语句,即使有成千上万的代码复杂度都是 O(1);

2.O(logn)、O(nlogn)

对数阶时间复杂度很是常见,同时也是一种比较难分析的一种时间复杂度;如:

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

可以了解 3 行的代码循环的执行次数是最多的;算出代码执行多少次 就知道整段代码的时间复杂度;

从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当大于 n 时,循环结束。还记 得我们高中学过的等比数列吗?实际上,变量 i 的取值就是一个等比数列。如果我把它一个一个列 出来,就应该是这个样子的:

21 * 22 * 23* 24..... 2x = n

所以 x = log(n2)n; 这段代码的复杂度就是 O(log2n);

在看下面的 代码

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

根据刚才我们分析,可以知道 x = log3n;这段代码的复杂度就是 O(logn3n);

实际上不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记

为 O(logn);

我们知道,对数之间是可以互相转换的,log3n 就等于 log3n * log2n,所以 O(log3n) = O(C * log2n),其中 C=log32 是一个常量。基于我们前面的一个理论:在采用大 O 标记复杂度的时候,可 以忽略系数,即 O(Cf(n)) = O(f(n))。所以,O(log2n) 就等于 O(log3n)。因此,在对数阶时间复杂度 的表示方法里,我们忽略对数的“底”,统一表示为 O(logn)。

O(nlogn): 根据乘法法则,如果一段代码的时间复杂度是 O(logn),我们循环 n 遍,时间复杂度就变成了 O(nlogn); 常见的n(nlogn):归并排序、快速排序时间复杂度都是 O(nlogn)

空间复杂度分析

前面我们讲过,时间复杂度全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系,类比一下,空间复杂度全称就是渐进空间复杂度;表示算法的存储空间与数据规模之间的增长关系;

1 function print(n) {
2   let a = 0;
3  let arr:[] = new Array()
4  for(let i = 0;i < n ;i ++) {
5    arr[i]= i*i
6 }
7}

跟时间复杂度一样,我们可以看到 3 行代码中,我们申请了一个大小是 n 的数组空间来存储变量 i;但是这是常量阶的 跟数据规模 N 没有关系;所以忽略;除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。

我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都 用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。所以,对于空间复杂度,掌握刚我 说的这些内容已经足够了。

常用的复杂度级别

多项式阶:

随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,

O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n^2)(平

方阶)、O(n^3)(立方阶)

非多项式阶:

随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括,

O(2^n)(指数阶)、O(n!)(阶乘阶)

自身学习记录