数据结构和算法,本身就是要解决 “快” 和 “省” 的问题。考量的指标分别就是 “时间复杂度” 和 “空间复杂度”。
时间复杂度表示代码执行时间随着数据规模增长的变化趋势,也叫渐进时间复杂度。
空间复杂度,全称渐进空间复杂度,表示算法的存储空间和数据规模之间的增长关系。
事后统计法:简单来说,就是让代码在实际的平台跑一遍。
事后统计法有以下局限性:
1,测试结果非常依赖测试环境
2,测试结果受数据规模的影响很大
T(n) = O(f(n))
T(n)表示代码的执行时间,n表示数据规模的大小,f(n)表示每行代码执行的次数总和。
代码示例:
int cal(int n){
int sum = 0;
int i = 1;
for(; i <= n; ++i){
sum = sum + i;
}
return sum;
}
第2、3行代码都是常量级的执行时间,与n的大小无关,所以对于复杂度并没有影响。循环执行次数最多的是第4、5两行代码。
总的时间复杂度是O(n);
代码示例:
int call(int n){
int sum_1 = 0;
int p = 1;
for(; p < 100; ++p){
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for(; q < 100; ++q){
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for(; i <= n; ++i){
j = 1;
for(; j <= n; ++j){
sum_3 = sum_3 + i*j;
}
}
return sum_1 + sum_2 + sum_3;
}
第一段:sum_1部分,属于常量的执行时间。对于一个执行次数固定的代码,即使循环10w,100w次,只要是一个已知数,跟n无关,照样是常量级的执行时间。
第二段和第三段:时间复杂度分别是O(n)和O(n2).
总的时间复杂度等于量级最大的那段代码的时间复杂度。上例中:T(n) = O(n);
代码示例:
int cal(int n){
int ret = 0;
int i= 1;
for (; i < n; ++i){
ret = ret + f(i);
}
}
int f(int n){
int sum = 0;
int i = 1;
for(; i < n; ++i){
sum = sum + i;
}
return sum;
}
以上示例中,cal()中嵌套调用了f(),复杂度为O(n)的函数嵌套调用复杂度为O(n)的函数,总的复杂度T(n) = T1(n) *T2(n) = O(n*n) = O(n2)。
多项式量级:
非多项式量级:
以下几个常见的多项式时间复杂度:
1,常量阶 O(1)
执行次数是一个确定数字的代码,即执行时间不随n的增长而增大,和n没有关系,都记作 O(1)
2, O(logn)、 O(nlogn)
示例代码:
i = 1;
while( i <= n ){
i = i*2;
}
以上代码,当2的k次方大于n时,循环终止,即执行次数:
对于,如果一段代码的时间复杂度是,我们循环执行n次,则时间复杂度就是。
3, 、
实例代码:
in cal(int m, int n){
int sum_1 = 0;
int i = 1;
for(; i < m; ++i){
sum_1 = sum_1 +i;
}
int sum_2 = 0;
int j = 1;
for(; j < n; ++j){
sum_2 = sum_2 +j;
}
return sum_1 + sum_2;
}
以上,无法事先评估 m 和 n 谁的量级大,所以无法利用加法准则,上面代码的时间复杂度就是
同理,当 m和n的代码出现嵌套,且无法判断m和n谁的量极大,时间复杂度对应的就是
空间复杂度,全称渐进空间复杂度,表示算法的存储空间和数据规模之间的增长关系。
代码实例:
void print(int n){
int i = 0;
int[] a = new int[n];
for(i; i < n; ++i){
a[i] = i * i;
}
for(i = n-1; i >= 0; --i){
print out a[i]
}
}
以上代码中,第二行i对应申请了一个空间存储变量,属于常量阶,和数据规模n没有关系,因此可以忽略。
第3行申请了一个大小为n的int 类型数组,除此之外,剩下的代码都没有占用更多的空间。
所以整段代码的空间复杂度就是
常见的空间复杂度是 、和,像、 这样的对数阶复杂度平时都用不到。