通过字面意思可以知道
- 时间复杂度:就是说执行算法需要消耗的时间长短,越快越好。
- 空间复杂度:就是说执行当前算法需要消耗的存储空间大小,也是越少越好
表示方法
我们一般用“大O符号表示法”来表示时间复杂度:T(n) = O(f(n))
n是影响复杂度变化的因子,f(n)是复杂度具体的算法。
时间和空间复杂度意义
评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。
可能有的开发者接触时间复杂度和空间复杂度的优化不太多(尤其是客户端),但在服务端的应用是比较广泛的。在巨大并发量的情况下,小部分时间复杂度或空间复杂度上的优化都能带来巨大的性能提升,是非常有必要了解的。
时间复杂度
常见的时间复杂度量级如下:
- 常数阶O(1)
- 线性阶O(n)
- 对数阶O(logN)
- 线性对数阶O(nlogN)
- 平方阶O(n²)
- 立方阶O(n³)
- K次方阶O(n^k)
- 指数阶(2^n)
1. 常数阶O(1)
int a = 1;
int b = 2;
int c = 3;
大O符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的。
如上代码即使成千上万,其算法并未边长,其时间复杂度仍为O(1)。
- 线性阶O(n)
for(i = 1; i <= n; i++) {// 执行n次
j = i;// 执行n次
}
n为几,则for内部代码块就需要运行多少次,所以它的时间复杂度其实是O(n)。
3. 对数阶O(logN)
int i = 1;
while(i < n) {
i = i * 2;
}
可以看到每次循环的时候 i 都会乘2,那么总共循环的次数就是log2n,因此这个代码的时间复杂度为O(log2n)。
这里的底数对于研究程序运行效率不重要,写代码时要考虑的是数据规模n对程序运行效率的影响,常数部分则忽略,所以成了O(logn)
同样的,如果不同时间复杂度的倍数关系为常数,那也可以近似认为两者为同一量级的时间复杂度。
4. 线性对数阶O(nlogN)
for(m = 1; m < n; m++) {
i = 1;
while(i < n) {
i = i * 2;
}
}
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
5. 平方阶O(n²)
for(x = 1; i <= n; x++){//执行你次
for(i = 1; i <= n; i++) {//执行 N*n次
j = i; //执行 N*n次
}
}
把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
6.立方阶O(n³)、K次方阶O(n^k)
参考上面的O(n²) 去理解就好了,O(n³)相当于三层n循环,其它的类似。
时间复杂度分析
通常用T(n)表示代码执行时间,n表示数据规模大小,f(n)表示代码执行综合次数。
在O(n)例子中执行次数为2n,如果没行执行时间t,则执行时间2nt,可以表示f(n)=2nt,
在O(n²)例子中执行次数为2n,如果没行执行时间t,则执行时间2nt,可以表示f(n)=(2n²+n)t。
代码的执行时间 T(n)与每行代码的执行次数 n 成正比,人们把这个规律总结成这么一个公式:T(n) = O(f(n))。大O时间复杂度并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。所以,也叫作渐进时间复杂度,简称时间复杂度。
当n变得越来越大时,公式中的低阶,常量,系数三部分影响不了其增长趋势,所以可以直接忽略他们,只记录一个最大的量级就可以了,所以两个例子实际他们的时间复杂度应该记为:T(n)=O(n) ,T(n)=O(n*n)
空间复杂度
1. 空间 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)。
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
2. 空间 O(n)
int[] arr = new int[n]
分配空间只和n有关
稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
如下冒泡排序中将条件修改为if (arr[j] >= arr[j + 1]),则稳定性将从稳定变为不稳定。
if (arr[j] > arr[j + 1]) {