常用的几种复杂度级别(复杂度从低到高排序):
- O(1) 常数级
- O(logn) 对数级
- O(n) 线性级
- O(nlogn) 线性对数级
- O(n2 n3 nk) k次方级
- O(n!) 阶乘级
- O(2n) 指数级
时间复杂度全称渐进时间复杂度,表示算法的运行时间与数据规模之间的增长关系
空间复杂度全称渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系
O(1)
int a = 0
O(logn)
int i = 1;
while(i <= n){
i = 2*i;
}
O(n)
for(int i = 0; i < n; i++)
n = i;
O(nlogn)
for(int i = 0; i < n; i ++){
int y = 1;
while(y <= n){
y = y * 2;
}
}
O(n2 n3 nk)
for(int i = 0; i< n; i++){
for(int j = 0; j < n; j++){
sum += j;
}
}
O(2n)
int fun(int n){
int sum = 0;
if(n < 1)
return sum;
for(int i = 0; i < 2; i++){
sum += fun(n - 1) + i;
}
return sum;
}
O(n!)
int fun(int n){
int sum = 0;
for(int i = 0; i < n; i++){
sum += fun(n - 1) + i;
}
return sum;
}
空间复杂度比较常用的是O(1), O(n), O(n2),相对比较简单。
时间复杂度在面对一些比较复杂的情况分析时有几个比较重要的知识点,最好情况时间复杂度 最坏情况时间复杂度 平均情况时间复杂度 均摊时间复杂度,比如下面的算法
// n表示数组array的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
上述代码是在一个数组中查找特定数字的算法,这时就有两种比较极端的情况,一个是数组的第一个数字就是x,那时间复杂度是O(1),还有一种是把数组遍历一遍也没有找到x,这时时间复杂度是O(n)。所以这里面就要引入最好情况时间复杂度,最坏情况时间复杂度和平均情况时间复杂度的概念。对于这种时间复杂度不一致的情况,我们使用平均情况复杂度来处理。那么平均情况复杂度该如何计算咧?
将所有情况循环需要执行的次数加起来,再除以情况数
1+2+3+…+n+n/n+1 = n(n+3)/2(n+1)
可以得到平均数是n(n+3)/2(n+1),用大O表示表示的时候我们可以省略到常数,系数,低阶。简化之后就是O(n),所以这个平均情况复杂度就是O(n);
这种平均情况复杂度在计算的时候有一点问题,那就是没种情况发生的概率是不一样的,假设X在数据组中和不在数组中的概率是1/2,假设要查找的数据出现在各个位置的概率是一样的,所以要查找的数据出现在0~n-1任意位置的概率是1/2n。这是平均时间复杂度的计算就是
这是加权平均值,叫加权平均情况时间复杂度。
均摊时间复杂度
// array表示一个长度为n的数组
// 代码中的array.length就等于n
int[] array = new int[n];
int count = 0;
void insert(int val) {
if (count == array.length) {
int sum = 0;
for (int i = 0; i < array.length; ++i) {
sum = sum + array[i];
}
array[0] = sum;
count = 1;
}
array[count] = val;
++count;
}
上面这个算法和前面的平均时间复杂度的算法不一样,在大多数情况下时间复杂度都是O(1),只有在一种极端的情况下时间复杂度才是O(n)。这种情况就不需要用平均时间复杂度的计算方法来计算了。上面的算法每有一次O(n),就会有n-1次O(1),我们把这一次耗时多的操作均摊到其他n-1次耗时少的操作上,这个算法的均摊时间复杂度就是O(1).均摊时间复杂度就是一种特殊的平均时间复杂度