时间复杂度
执行效率是衡量算法优劣的一个重要指标,一般来说我们把代码运行一遍就能得到算法执行的时间和占用的内存大小,但这其实很不准确,结果既依赖于运行环境硬件设备又被测试规模影响。
所以我们需要一种分析方法去计算算法的运行效率,这就是大O复杂度表示法,比如我们经常看见O(n)之类的。
举个简单例子,一段累加代码:
public int count(int n){ int sum = 0; for(int i = 1; i <= n; i++) sum += i; return sum;}
从CPU角度来说,每段代码都意味着读数据-计算数据-写数据,假设每行代码的运行时间为unit_time,那么for循环了n遍,所以运行时间为2n*unit_time,再加上第一句赋值的时间,总运行时间为(2n+1)*unit_time。
所以可以观察到代码的执行时间 T(n) 与每行代码的执行次数成正比,如果将代码的执行次数记为f(n),代码执行时间和执行次数的关系记为O,那么将其总结为公式:
T(n) = O(f(n))
大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。
比如第一个例子T(n) = O(2n+1),如果n非常大的时候,公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了,如果用大 O 表示法表示第一个例子的时间复杂度,就可以记为:T(n) = O(n)。
所以时间复杂度分析的重点就是只关注循环执行次数最多的一段代码,忽略执行次数公式中的低阶、常量、系数。
另外再分析下O(logn)、O(nlogn)这种类型的时间复杂度。
int i = 0;while(i <= n){ i *= 2;}
上述代码运行的次数和2的指数有关,就是运行了x次,2x = n的时候循环结束,那么运行次数x为log2n,很简单就可以得出时间复杂度为O(log2n)。
实际上,不管是以2为底、以3为底,还是以10为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)。
假如将上述代码改成i *= 3,那么算法执行了log3n次,根据对数转换的公式,log3n 就等于 log32 * log2n 时间复杂度就相当于log32*O(log2n ),忽略掉系数log32就是O(log2n ),因此,在对数阶时间复杂度的表示方法里,我们忽略对数的底,统一表示为O(logn)。
如果一段代码的时间复杂度是O(logn),又循环执行n遍,时间复杂度就是O(nlogn) 了。而且O(nlogn) 也是一种非常常见的算法时间复杂度。比如归并排序和快速排序。
空间复杂度
类比之前的时间复杂度,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
这个分析就比较简单了,比如代码申请了一个int[n]的数组,忽略其他低阶等那么空间复杂度也就是O(n)。
再举个例子比如存储一个二进制数n,那么其空间复杂度就是是O(logn)。