学习算法、数据结构,首先看看复杂度相关;
复杂度分析(上) 如何分析、统计算的的执行效率和资源消耗?
什么是复杂度分析
-
数据结构和算法解决的是 ‘ 如何让计算机更快时间、更省空间’的问题;
-
从执行时间和占用空间两个维度来分析数据结构 和算法的性能;
-
分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度;
-
复杂度描述的是算法执行时间(或占用空间)与数据规模的增长关系;
为什么要分析复杂度?
通过统计、监控,就能得到算法执行的时间和占用的内存大 小;这叫做事后统计法; 有以下缺点;
- 测试结果非常依赖测试环境: 高端的处理器 速度快于低端的处理器
- 测试结果受数据规模的影响很大: 高效的排序有时候受数据的的多少影响;
综上:
1.复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点;
2.掌握复杂度分析,在我们开发过程中将能编写出性能更优的代码,有利于提高效率,降低系统开发和维护成本。
大 O 复杂度表示法
大 O 时间复杂度实际上并不表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫渐进时间复杂度;简称时间复杂度;
1 .来源:
算法的执行时间与每行代码的执行次数成正比,用T(n) = O(f(n))表示,其中T(n)表示算法执行总时
间,f(n)表示每行代码执行总次数,而n往往表示数据的规模。
2.特点
在计算复杂度的过程中,低阶,常量,系数三部分并不在左右增长趋势,所以可以忽略,我们就记录一个最大量级就可以了;
时间复杂度分析
- 只关注循环执行次数最多的一段代码,这段代码执行次数的 n 的量级,就是整段要分析的时间复杂度;
- 加法法则:总复杂度等于量级最大的那段代码的复杂度;
- 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积;
综上:
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!)(阶乘阶)
自身学习记录