前一篇文章算法复杂度分析(上)讲述了复杂度的大 O 表示法和几个分析原则,这篇文章咱们来说讲另外几种复杂度,最好状况时间复杂度(best case time complexity)、最坏状况时间复杂度(worst case time complexity)、平均时间复杂度(average case time complexity)和均摊时间复杂度(amortized time complexity)。算法
顾名思义,这两种时间复杂度指的是特殊状况下的时间复杂度。咱们看下面的例子:数组
// n 表示数组 array 的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
复制代码
这段代码实现的功能是,在数组 array 中寻找变量 x 第一次出现的位置,若没有找到,则返回 -1;不然返回位置下标。bash
用上一篇文章的方法显然是没法分析这段代码的复杂度的。由于,不一样状况下的时间复杂度是不一样的。当数组中第一个元素就是要找的 x 时,时间复杂度是 O(1);而当最后一个元素才是 x 时,时间复杂度则是 O(n)。微信
为了表示代码在不一样状况下的时间复杂度,就须要引入三个概念:最好状况时间复杂度、最坏状况时间复杂度和平均状况时间复杂度。函数
其中,最好状况时间复杂度就是在最理想状况下执行代码的时间复杂度,它的时间是最短的;最坏状况时间复杂度就是在最糟糕状况下执行代码的时间复杂度,它的时间是最长的。post
最好、最坏时间复杂度反应的是极端条件下的复杂度,发生的几率不大,不能表明平均水平。那么为了更好的表示平均状况下的算法复杂度,就须要引入平均时间复杂度。ui
继续用前面 find 函数为例,假设变量 x 在和不在数组 array 中的几率分别为 1 / 2;当存在于数组中时,在每一个位置的几率均等,为 1 / n。那么,平均状况时间复杂度就能够用下面的方式计算:spa
((1 + 2 + ... + n) / n + n) / 2 = (3n + 1) / 4
3d
这个值就是几率论中的加权平均值,也叫指望值。因此平均状况时间复杂度也叫加权平均时间复杂度或指望时间复杂度。可见,find 函数的平均时间复杂度为 O(n)。code
大多数状况下,不须要区分最好、最坏、平均状况时间复杂度,只用一个复杂度就能够知足需求了。只有当同一块代码在不一样状况下,时间复杂度有数量级上的区别时,才须要考虑这三种复杂度。
由上面咱们能够知道,平均时间复杂度只有在某些特殊的时候才会用到。均摊时间复杂度的应用场景比它更为特殊。均摊时间复杂度是指,当大部分状况下时间复杂度都很低,只有个别状况下时间复杂度比较高时,而且这些操做之间存在着先后连贯的时序关系,这时候,能够将较高时间复杂度的操做耗时均摊至时间复杂度较低的操做上。这种分析方法叫作摊还分析法,获得的复杂度叫作均摊时间复杂度。
并且,在可以应用均摊时间复杂度分析的场合,通常均摊时间复杂度就等于最好状况时间复杂度。
例如:
int[] array = new int(n);
int count = 0;
void addLast (int val) {
if (count == array.length) {
int[] newArray = new int(2 * n);
for (int i = 0; i < 2 * n; i++) {
newArray[i] = array[i];
}
newArray[count] = val;
array = newArray;
} else {
array[count] = val
}
count++;
}
复制代码
这段代码实现的功能是往数组的末尾增长一个元素,若是数组没有满,直接日后面插入元素;若是数组满了,即 count == array.length
,则将数组扩容一倍,而后再插入元素。
例如,数组长度为 n,则前 n 次调用 addLast() 复杂度都为 O(1);第 n + 1 次则须要先进行 n 次元素转移操做,而后再进行 1 次插入操做,复杂度为 O(n)。并且很容易看出,O(1) 复杂度的操做和 O(n) 复杂度的操做出现频率是有规律的,每 n 次 O(1) 操做后会跟随一个 O(n) 操做。
那么,就能够将 O(n) 操做的复杂度均摊至每次 O(1) 操做中,均摊下来,这组操做的须要进行 (n + n * 1) / (n + 1) = 2n / (n + 1)
次操做,因此均摊复杂度为 O(1)。
本文首发自微信公众号《代码写完了》