1. 时间复杂度的定义
算法的时间复杂度可以用一个函数来定性的表示程序运行时间。时间运行短的效率高,时间运行长的效率低。
1 | x=T(n)=O(f(n)) |
n
表示输入规模。假设现在有一个累加求和的算法,目前有两个输入规模:1
100和11000。毫无疑问,后者的输入规模比前者要大。x
表示代码执行次数。核心代码执行次数,执行次数越多,表示需要花费的时间越长。
总体来说,我们所追求的理想目标是随着输入规模(n
)的增大,核心代码执行次数(x
)不会上升得太快,最好是不要上升。
2. 常见的时间复杂度
按照时间复杂度由小到大排序,常见的时间复杂度如下:
常数阶O(1)
1
2//无论i增加到多大,得到结果都只需要执行一行代码
int n = n + 1对数阶O(logN)
线性阶O(n)
1
2
3
4
5
6//需要n次循环执行代码才能得到结果
for(i=1; i<=n; ++i)
{
j = i;
j++;
}线性对数阶O(nlogN)
平方阶O(n²)
1
2
3
4
5
6
7
8
9//需要n²次循环执行代码才能得到结果
for(x=1; i<=n; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}立方阶O(n³)
K次方阶O(n^k)
指数阶(2^n)
3.如何计算时间复杂度?
3.1 推导大O阶的方法
用常数1取代运行时间中的所有加法常数。
在修改后的运行函数中,只保留最高阶项。
如果最高阶项存在且不是1,则去除与这个项相乘的常数。
3.2 时间复杂度log(n)是怎么算的?
1 | let count = 1; |
考虑上面这个程序,由于每次count
乘以2
之后,就距离n
更近了一分。也就是说,有x
个2
相乘后大于n
, 则会退出循环。 所以我们可以得到2^x =n
。最终我们需要得到x
也就是f(n)
,因此式子必须要进行转换。根据对数运算,得到x=log 2n。再把底数给省略(影响较小,不重要),这个循环的时间复杂度为O(logn)。
另外一个由二分查找算法推算出log(n)的例子可以看这里。
3.3 什么是算法的排序稳定性
通俗地讲就是,两个数相等,排序前和排序后位置不变就是稳定的。如果要下一个定义的话,可以这么说:一列数中,如果Ai = Aj,Ai位于Aj的前置位,那么经过升降序排序后Ai仍然位于Aj的前置位。
稳定性对于原始数据排序本来就无意义的数据来说,稳定性不重要。但是原始数据排序有意义的情况下,可能就重要了。比如商品先按照价格排序,再按照销量排序。则第二次排序中,如果排序算法是稳定的,那么可以保证销量相同的情况下,按照价格排序。
4. 常见排序和查找算法的时间复杂度
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
直接选择排序 | O(n²) | O(n²) | O(n) | O(1) | 不稳定 |
直接插入排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n²) | O(nlogn) | O(nlogn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
希尔排序 | O(nlogn) | O(ns) | O(n) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(N*M) | O(N*M) | O(N*M) | O(M) | 稳定 |
- 冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素(除开已经排过放到队列尾部的元素)重复以上的步骤。 - 选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。重复第二步,直到所有元素均排序完毕。 - 插入排序
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
1 | public int[] sort(int[] a) { |
快速排序
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
1 | /** |
- 归并排序
1 | /** |
查找算法 | 平均时间复杂度 | 查找条件 |
---|---|---|
顺序查找 | O(n) | 无序或有序队列 |
二分查找(折半查找) | O(logn) | 有序数组 |
二叉排序树查找 | O(logn) | 二叉排序树 |
哈希表法(散列表) | O(1) | 先创建哈希表(散列表) |
分块查找 | O(logn) | 无序或有序队列 |
1 | public static int search(int[] array, int lowIndex, int highIndex, int searchValue) { |
5、空间复杂度
空间复杂度就是用来衡量空间使用量的。空间复杂度比较常用的有:O(1)、O(n)、O(n²)。