算法复杂度分为时间复杂度和空间复杂度。时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。
一、时间复杂度
在介绍时间复杂度之前,先引入时间频度的概念:
一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。
每个算法花费的时间与算法中语句的执行次数成正比,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度,记为T(n)。n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n) = O( f(n) ),称O( f(n) )为算法的渐进时间复杂度,简称时间复杂度。
一般描述时间复杂度,有如下几个数量级(见下表):
比如常数级别,算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。
<?php
$x = 91;
$y = 100;
while ($y > 0) {
if($x > 100) {
$x = $x – 10;
$y = $y – 1;
} else {
$x ++;
}
}
虽然这个程序运行了循环110次,但是这个程序和n无关,它只是一个常数阶的函数,所以时间复杂度是O(1)。
再比如线性级别、平方级别和立方级别,当有若干个循环语句时,算法的时间复杂度由循环语句中的嵌套层数决定。一个循环,时间复杂度是O(n),两个循环,时间复杂度就是O(n2),三个循环,时间复杂度就是O(n3)。
表中的这些增长数量级并不是一个准确的性能评价,但是可以理解为一个近似值,时间的增长近似于logN、NlogN的曲线,见下图。可以看到随着问题规模的增加,不同级别的运行时间有很大的差异。
二、空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O( f(n) ),其中n为问题的规模。比如直接插入排序的空间复杂度是O(1) ;而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
三、稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri = rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
四、各种排序的时间复杂度、空间复杂度和稳定性总结
算法 | 冒泡排序 | 选择排序 | 插入排序 | 希尔排序 | 归并排序 | 快速排序 | 基数排序 | 堆排序 | |
时间复杂度 | 平均 | O(n²) | O(n²) | O(n²) | O(nlogn) | O(nlogn) | O(nlogn) | O(d(r+n)) | O(nlog2n) |
最好 | O(n) | O(n²) | O(n) | O(n) | O(nlogn) | O(nlogn) | O(d(n+rd)) | O(nlog2n) | |
最坏 | O(n²) | O(n²) | O(n²) | O(n²) | O(nlogn) | O(n²) | O(d(r+n)) | O(nlog2n) | |
空间复杂度 | O(1) | O(1) | O(1) | O(1) | O(n) | O(log2n) | O(rd+n) | O(1) | |
稳定性 | 稳定 | 不稳定 | 稳定 | 不稳定 | 稳定 | 不稳定 | 稳定 | 不稳定 | |
注:基数排序的复杂度中,r代表关键字的基数,d代表长度,n代表关键字的个数。 |