当先锋百科网

首页 1 2 3 4 5 6 7

算法的时间复杂度表示算法运行所需要的时间

  • 大O表示法
  • 递归算法中时间复杂度分析

大O表示法

大O表示法 是一种体现算法时间复杂度的记法,如果用n表示数据规模,那么O(f(n)表示算法说需要执行的指令数(消耗的时间)和 f(n) 成正比。大O表示法指出了算法执行的最低上限。(大O表示法的前边省略了一个常数)。

例子:有一个字符数组,将数组中的每一个字符串按照字母排序,再将整个字符串数组按照字典序排序求这个算法的时间复杂度。
分析:假设每个字符串的最大长度是s,字符数组中一共有n个字符串。
* 对于一个字符串来说,进行排序的复杂度是O(s*log s),则整个字符数组的复杂度是O(n*s*log s)
* 对于整个字符串数组排序的复杂度是O(n*log n),而进行字典排序时每次最多进行s次比较,每次比较都是常数时间,所以复杂度是O(s*n*log n)
* 综上:整个算法的复杂度是O(s*n*(log n +log s)

常见的大O阶有常数阶O(1),线性阶O(n),平方阶O(n²),对数阶O(logn),nlogn阶O(nlogn)等等。如果有在有相同规模的n,则只保留最高阶,如O(n+n²) = O(n²)。

线性阶

随数据规模n线性增长。如下面的代码:

    for(int i=0;i<n;i++){
    //时间复杂度为O(1)的算法
    ...
    }       

对数阶

接着看如下代码:

int number=1;
while(number<n){
    number=number*2;
    //时间复杂度为O(1)的算法
    ...
}

可以看出上面的代码,随着number每次乘以2后,都会越来越接近n,当number不小于n时就会退出循环。假设循环的次数为X,则由2^x=n得出x=log₂n,因此得出这个算法的时间复杂度为O(logn)。

平方阶

下面的代码是循环嵌套:

for(int i=0;i<n;i++){   
  for(int j=i;j<n;j++){
     //复杂度为O(1)的算法
     ... 
      }
  }

需要注意的是内循环中int j=i,而不是int j=0。当i=0时,内循环执行了n次;i=1时内循环执行了n-1次,当i=n-1时执行了1次,我可以推算出总的执行次数为:

n+(n-1)+(n-2)+(n-3)+……+1 = n²/2+n/2

只保留最高阶,因此保留n²/2,并且去掉这个项的常数,1/2,最终这段代码的时间复杂度为O(n²)。

注意:
1.以下代码的复杂度仍为O(n),原因是只进行了30*n次基本操作。

for(int i=0;i<n;i++){   
  for(int j=0;j<30;j++){
     //复杂度为O(1)的算法
     ... 
      }
  }

2.以下代码的复杂度仍为O(n*log n)。

for(int i=1;i<n;i +=i){   
  for(int j=0;j<n;j++){
     //复杂度为O(1)的算法
     ... 
      }
  }
以上充分说明,应该时刻关注数据规模,而不是一些形式上的类似。

递归算法中时间复杂度分析

递归中进行一次递归调用

如下图所示,该二分查找只进行了一次递归调用。每次调用的复杂度为O(1),递归深度为O(log n),
故整个算法的复杂度为O(log n)。
这里写图片描述

一般来说,在递归函数中只递归一次递归调用,总体的复杂度为O(每个递归函数的时间复杂度*递归深度)

递归中进行多次递归调用

此时应该关注的是调用次数,如下面代码所示:此时可以通过数递归树上的节点。
这里写图片描述
比如当n=3时,它的递归树如下图 右半侧,更一般的结论如左半侧所示。
这里写图片描述

又比如,在归并排序中,排序的数据规模都会减少一半,所以递归深度时log n,但是每层进行排序的数据规模都是n,所以时间复杂度是O(n*log n).
这里写图片描述