1.为什么要进行复杂度分析
在编写和使用数据结构和算法的时候,怎么去比较它们之间的优劣,需要一个评判标准,目前主要的分析维度是两方面,时间和空间,什么意思呢,时间复杂度分析就是这个算法在一定的运算量级下的消耗时间,时间越少越优,空间复杂度分析可以看作是占用内存空间的大小,空间越小越优。
2.时间复杂度分析
大O复杂度表示法
如下代码
public int sum(int n) {
int sum = 0;//1
for (int i = 0; i < n; i++) {
sum = sum + i;//2
}
return sum;
}
假设,循环中的每次循环的单元时间是unit_time,那么上述代码中1处需要运行,1*unit_time,2处需要运行n*unit_time,总的时间是(n+1)*unit_time。这里时间运行时间只是一个预估值,不是非常精确,为什么这么说,因为在循环的int i = 0;这个操作也是运行了1此。
再如下代码
public int sum(int n) {
int sum = 0;//1
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {//2
sum = sum + i*j;//3
}
}
return sum;
}
在1处运行了1*unit_time,在2处运行了n*unit_time,3处运行了n*n*unit_time,总的时间是n*n + n + 1。
大O的表达式为T[n] = O(f(n)),以上两个示例使用大O表示分别为T[n] = O(n+1),T[n] = O(n*n + n + 1),大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,也叫作渐进时间复杂,简称时间复杂度。所以以上两个时间复杂度表达式并不是最终的复杂度,可以假设,在表达式O(n+1)这个表达式中,随着n的无限增大,1对整个表达式的影响可以忽略,比如当n=10000时,n+1也才10001,所以在大O中,可以使用O(n)这样表达准确的时间复杂度。以此推论O(n*n + n + 1)的大O表达式最终为O(n*n)。所以以上两端代码的时间复杂度分别为O(n)和O(n*n)。
推导大O表达式规则:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数得到的结果就是大O阶
常见的时间复杂度
执行次函数 | 阶 | 非正式术语 |
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n*n+2n+1 | O(n*n) | 平方阶 |
5logn + 20 | O(logn) | 对数阶 |
2n+3nlogn+19 | O(nlogn) | nlogn阶 |
6n*n*n + 2n*n+3n+4 | O(n*n*n) | 立方阶 |
2的n次方 | O(2的n次方) | 指数阶 |
最坏情况和平均情况
比如,一个长度为1000的数组,数组元素有序增大,搜索其中元素为3的索引位置,最好情况是在索引为0的位置找到,最坏的情况是在索引为999的位置找到。平均就是1000/2=500的位置找到。
最坏情况运行时间是一种保证,在应用中这是一种最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。
3.空间复杂度分析
空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
public int sum(int n) {
int sum = 0;//1
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = i;
}
return sum;
}
比如上面这段代码,申请了一个长度为n的数组,在遍历中给每个索引位置赋值。整段代码只占用了n长度的内存地址。没有内存扩展,所以空间复杂度为O(n)。对于空间复杂度的掌握了解就行了,很多时候都是以空间换时间。通常比较一个算法都是默认采用时间复杂度。