算法的时间空间复杂度和空间复杂度总结
时间频度
一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
如何获得T(n):
public static int factorial(int n){
int result = 1; //①
if(n == 0 || n == 1){ //②
return result; //③
}else{
for(int i = 2;i<=n;i++){ //④
result *= i; //⑤
}
return result; //6
}
}
上面的这个方法中,①语句执行一次;②虽然是if语句,但判断也需要执行一次;我们通常在计算算法效率时,做最坏打算,因此我们认为if不成立,执行else中的语句,④语句中包含三条语句,各执行了n-1次;⑤语句处于for循环中,执行了n-1次;⑥语句执行一次。因此,T(n)=1+1+4(n-1)+1=n-1
时间复杂度
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
在上面已经提到了求T(n)的方法,现在我们只需要知道怎样通过T(n)求出f(n),就得出时间复杂度O(f(n))
若把T(n)比作一棵树,那f(n)就是将树的枝叶修剪完的主干,修剪的规则如下:
- 常数省略,若T(n)本身就为常数,则f(n)=1
- 低次阶省略,例如T(n)=n^3+n^2+3,则f(n)=n^3
- 系数忽略,例如T(n)=3n^3+n^2+3,则f(n)=n^3
为什么会出现这样的规则,这时是因为在算法中,我们认为n趋于无穷大,因此常数、低次阶、系数对于T(n)的影响很小,所以省略。
平均时间复杂度:平均时间复杂度是指所有可能的输入实例均以等概率出现情况下该算法的运行时间
最坏时间复杂度:最坏情况下的时间复杂度称为最坏时间复杂度。一般讨论的时间复杂度均是最坏时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长
常见的时间复杂度
常见的时间复杂度有:常数阶O(1)、对数阶O(log2n)、线性阶O(n)、平方阶O(n^2)、立方阶O(n^3)、k次方阶O(n^k)、线性对数阶O(nlog2n)、指数阶O(2^n)、阶乘阶O(n!)
时间复杂度由小到大依次为:O(1)<O(log2n)<O(n)<O(n^2)<O(n^3)<O(n^k)<O(nlog2n)<O(2^n)<O(n!)
用图片表示部分时间复杂度随n的增长T(n)增长的曲线:
1.O(1)
a = 1;
b = a;
T(n)为一个常数,因此f(n)=1,故O(f(n))=O(1)
2.O(log2n)
i = 1;
while(i<n){
i *= 2;
}
设while循环执行次数为x,则2^x=n,x=log2n,因此T(n)=2log2n+1,f(n)=log2n,故O(f(n))=O(log2n)
3.O(n)
int a = 1;
for(int i = 0;i < n;i++){
a++;
}
for循环执行次数为n次,因此T(n)=4n+1,f(n)=n,故O(f(n))=O(n)。一个n次循环的for循环就是线性阶,两个n次循环的for循环就是平方阶,O(n^k)就是k个n次循环的for循环嵌套
4.O(nlog2n)
int i = 1;
int a = 1;
while(i<n){
for(int i = 0;i < n;i++){
a++;
}
i *= 2;
}
T(n)=2+2log2n+4nlog2n,f(n)=nlog2n,故O(f(n))=O(nlog2n)
调用方法时的时间复杂度的计算
调用方法时不参与循环,则可以将被调用方法的f(n)当做其T(n)加入到当前方法的T(n)中,已知factorial方法的时间复杂度为O(n)
public static void print(int n){
int a = 0;
for (int i = 0;i<n;i++){
a = i;
}
System.out.println(factorial(i));
}
public static int factorial(int n){
int result = 1;
if(n == 0 || n == 1){
return result;
}else{
for(int i = 2;i<=n;i++){
result *= i; //①
}
}
return result;
}
T(n)=1+4n+n,print的时间复杂度为O(n)
调用方法时参与循环,这种情况下就要考虑循环条件,修改上面的部分代码:
public static void print(int n){
for (int i = 0;i<n;i++){
System.out.println(factorial(i));
}
}
设print方法的时间频度为T(n),①被调用的次数为0+0+1+2+...+n=(n^2+n)/2,故T(n)=3n+(n^2+n)/2,f(n)=n^2,所以O(n^2)。
空间复杂度
一个算法的空间复杂度S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。
一个算法在计算机存储器上所占用的存储空间,包括①存储算法本身所占用的存储空间,②算法的输入输出数据所占用的存储空间,③算法在运行过程中临时占用的存储空间。
- 存储算法本身所占用的存储空间由算法代码的书写长短有关,优化代码量可节约空间
- 算法的输入输出数据所占用空间,通常解决一个问题的不同算法的输入输出数据基本相同,因此不用考虑优化
- 算法在运行过程中临时占用的存储空间,例如有些算法通过数组解决,有些通过链表解决等,不用的算法占用的临时存储空间不同,需要关注优化。
算法的空间复杂度也用O表示,指的是该算法所占用的空间随问题规模n的增长所变化的情况,O(1)表示算法所占用空间不随n的增长而增长;O(n)表示算法所占用空间随n的增长线性增长
在衡量一个算法的效率时,主要考虑时间复杂度,因为时间复杂度直接与用户体验相关,用空间换取时间也是可行的。