算法—浅析最好、最坏、平均、均摊时间复杂度

411 阅读5分钟

对大O复杂度分析有一定基础的情况,可以进一步了解 最好情况时间复杂度(best case time complexity)、 最坏情况时间复杂度(worst case time complexity)、 平均情况时间复杂度(average case time complexity)、 均摊时间复杂度(amortized time complexity)。如果这几个概念你都能掌握,那对你来说,复杂度分析这部分内容就没什么大问题了。

1、最好、最坏时间复杂度

先分析下下面的例子:

void find (int[] array,int x){
  int pos = -1;
  for (i=0;i<array.length;i++){
    if(array[i] == x) {
      pos = i;
    }
  }
}

按照大O分析法,这段代码的复杂度就为O(n);n代表数组的长度。但其实我们在查找数组中某个元素时,不一定非要把整个数组都遍历一遍,可以进行简单优化,如:

void find (int[] array,int x){
  int pos = -1;
  for (i=0;i<array.length;i++){
    if(array[i] == x) {
      pos = i;
      break;
    }
  }
}

优化后的代码的时间复杂度还是O(n)吗?其实看一下也不尽然。这样就可以引进三个新的概念:最好时间复杂度、最坏时间复杂度和平均时间复杂度。
可以比较直观的分析得到两种情况:

  • 当x位于数组的最前面的下标,极端一些就在下标0的位置,那么时间复杂度就为O(1),这就是最好时间复杂度。
  • 另一种情况则是所查询的x不存在与数组之中,那么就要遍历整个数组,这样时间复杂度就为O(n)。这种极度糟糕的情况对应的就是最坏时间复杂度。

2、平均时间复杂度

依旧拿上面例子为主,发生最好和最坏的情况的概率还是很小的。为了更好的表示平均情况下的时间复杂度,就需要引入一个新的概念:平均情况时间复杂度,简称平均时间复杂度。

我们知道要找的x在数组和不在数组的准确概率计算是极其复杂的,我们概括性的假设二者的概率均为1/2。另外查找的x出现在0~(n-1)这n个位置的概率为1/n。那么考虑在数组或不在数组的概率,根据概率乘积法则,x出现在0~(n-1)这n个位置的概率为1/2n,由此可以推到出以下公式:
推到公式

这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。用大 O 表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度仍然是 O(n)。

平均时间复杂度看起来很复杂,又是引入概率又是需要推导。不过平常情况下只需要一种复杂度就可以满足需求。除非是在同一个代码块因不同情况出现,时间复杂度有量级上的差距,才需要用这三种时间复杂度进行分析。

3、均摊时间复杂度

均摊复杂度听起来和平均时间复杂度十分相似,而且也经常容易混淆。那具体均摊复杂度是什么呢?我们还是通过具体例子来了解一下,以及它的分析方法:摊还分析/平摊分析.

int[] array = new int[n];
int count = 0;
void insert(int element){
  int sum;
  if(count==array.length){
    for(int i=0;i<array.length;i++){
      sum = sum + array[i];
    }
    array[0] = sum;
    count = 1;
  }
  array[count] = element;
  count++;
}

上面是一个很简单的数组插入数据的代码块,当数组满了时,将数组所有内容求和放入下标为0的位置,再继续插入数据。按照大O分析法,我们进行最好、最坏和平均时间复杂度分析。

通过分析代码,我们可知。只有在数组满时才会进行数组遍历并求和,此时的时间复杂度为O(n),其余时间的复杂度均为O(1)。所以最坏时间复杂度为O(n),最好时间复杂度为O(1)。
那么接下来进行平均时间复杂度分析。假设插入的数组位于0~(n-1)间位置的概率是一样的均为1/n,由此推导出平均时间复杂度为O(1),以下为推导过程:
推到公式

到此最好、最坏、平均时间复杂度进阶推导出来,其实这个例子的时间复杂度大可不必如此麻烦,还引入概率。我们仔细分析一下find()和insert()两个方法的区别。

  • O(n)的出现是小概率事件,O(1)反而出现的概率很大。
  • O(n)和O(1)出现是极其有规律的。当出现一次O(n)后,后面就会跟着n-1个O(1)。循环往复。

这种情况其实就没必要引入概率来进行平均时间复杂度的计算。针对这种特殊的场景,引入一种更简单的分析方法:摊还分析法,由此引出新的时间复杂度:摊还时间复杂度。具体分析如下:在了解O(n)和O(1)出现的规律后,可以把耗时多的O(n)的时间均摊到接下来的n-1次操作上,这样下来,摊还时间复杂度就为O(1)。

摊还时间复杂度可以针对特殊的场景进行简单化的分析,如:针对特殊的数据结构,在一段连续操作的代码块中,其中大多数操作耗时很短,只有极少数的操作耗时较长,就可以针对性的适用摊还分析进行时间复杂度分析。个人认为摊还时间复杂度可以算作一种特殊的评价时间复杂度