课程:《复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度》 总结
有时候,代码的时间复杂度在不同情况下会出现量级的差异。为了更全面、更准确的描述代码的时间复杂度,需要引入下面的概念。
四个复杂度分析的概念
- 最好情况时间复杂度(best case time complexity)
代码在最理想的情况下执行的时间复杂度。 - 最坏情况时间复杂度(worst case time complexity)
代码在最糟糕的情况下执行的时间复杂度。 - 平均情况时间复杂度(average case time complexity)
代码在所有情况下的复杂度的加权平均值,即加权平均时间复杂度 或 期望时间复杂度 - 均摊时间复杂度(amortized time complexity)
在一组连续的具有时序关系的操作中,如果将这一组操作看成是一个整体,那么就可以将其中较高复杂度代码的执行时间平摊到复杂度较低的代码上面。然后再进行复杂度分析后得出的就是均摊时间复杂度,这种分析方法叫做摊还分析。一般在这种特殊的场合中,均摊时间复杂度就是最好情况时间复杂度。
下面以课后题作为案例分析:
// 全局变量,大小为10的数组array,长度len,下标i。
int array[] = new int[10];
int len = 10;
int i = 0;
// 往数组中添加一个元素
void add(int element) {
if (i >= len) { // 数组空间不够了
// 重新申请一个2倍大小的数组空间
int new_array[] = new int[len*2];
// 把原来array数组中的数据依次copy到new_array
for (int j = 0; j < len; ++j) {
new_array[j] = array[j];
}
// new_array复制给array,array现在大小就是2倍len了
array = new_array;
len = 2 * len;
}
// 将element放到下标为i的位置,下标i加一
array[i] = element;
++i;
}
-
最好情况时间复杂度
最好情况就是当前数组中有足够的空间存放添加的数据,即直接执行添加数据操作,这样代码的执行次数总和为常量级,所以最好时间复杂度为 O(1)。 -
最坏情况时间复杂度
最坏的情况是数组已满,需要重新申请两倍新数组空间和循环拷贝当前数组的元素到新数组中。假设当前数组的长度为 n,根据大O复杂度分析方法可得代码的时间复杂度为 O(n)。 -
平均情况时间复杂度
分析可知,添加的元素可能存放的位置有 0、1、2、3、4…n-1 这 n 种情况,以及数组满了需要新申请空间的情况,即总共 n+1 种情况,前面不需要申请空间的 n 种情况的时间复杂度都为 O(1),需要申请空间的情况下的时间复杂度(即最坏时间复杂度)为 O(n),假设每种情况的概率都相同,即 1/(n+1),那么 平均时间复杂度 就为:
T(n) = n 种情况的 ( O(1) x 1 n + 1 {1 \over n + 1} n+11 ) + 数组满了情况下的 ( O(n) x 1 n + 1 {1 \over n + 1} n+11 ) 即:
T(n) = O(1) x 1 n + 1 {1 \over n + 1} n+11 + O(1) x 1 n + 1 {1 \over n + 1} n+11 … O(1) x 1 n + 1 {1 \over n + 1} n+11 + O(n) x 1 n + 1 {1 \over n + 1} n+11 = O( 2 n n + 1 {2n \over n + 1} n+12n) = O(1)
-
均摊时间复杂度
分析代码可知,每进行 n 次 O(1) 的操作后,会紧跟一次 O(n) 的操作,如果将这些操作看作一个整体,将这一次 O(n) 的操作花费的时间平均在 n 次 O(1) 的操作上,也就相当于代码多进行了一次 O(1) 的操作,即 O(1) + O(1) = O(2) = O(1)。 -
下面是我从时间复杂度表示的意义出发,做的分析:
我们知道大O时间复杂度表示的是代码执行时间随数据规模增长的变化趋势。从这个角度出发,看这段代码。当数组的规模很大时,有足够的空间来存放添加的数据,那么代码的执行次数就会是常量级的。所以均摊时间复杂度为O(1),平均时间复杂度也为O(1).