1. 最好、最坏情况时间复杂度
举个栗子
// 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;
}
return pos;
}
这段代码的功能是:在一个无序的数组(array)中,查找变量 x 出现的位置。如果没有找到,就返回 -1。
这段代码的时间复杂度是O(n)。
把这段代码优化一下,如下
// 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;
}
对这段代码进行分析:如果数组中的第一个元素正好是要查找的变量x,那么时间复杂度是O(1),如果数组中的最后一个元素是要查找的变量x,那么时间复杂度是O(n)。由此引入3个概念:最好时间复杂度、最坏时间复杂度、平均情况时间复杂度。
最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度。
最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度。
2. 平均情况时间复杂度
借助上面那个栗子
查找变量x,有n+1种情况:在数组的0~n-1位置和 不在数组中。需要遍历的元素个数的平均值为1+2+3+4+…+n+n/n+1 = n(n+3)/2(n+1)。
大O标记法中可以省略系数、低阶、常量,简化后平均时间复杂度为O(n)。
问题在于:这n+1中情况出现的概率是不一样的。假设变量x在数组中和不在数组中的概率均为1/2,那么变量x在数组中的任意位置的概率为1/2n,如果我们把情况发生的概率也考虑进去,那么平均时间复杂度的计算变为:1 * 1/2n + 2 * 1/2n + 3 * 1/2n +… + n * 1/2n + n * 1/2 = 3n+1/4.这个值是概率论中的加权平均值(期望值),平均时间复杂度的全称应该为加权平均时间复杂度或者期望时间复杂度。
去掉系数和常量,平均情况时间复杂度仍为O(n)。
3. 均摊时间复杂度
举个栗子
// array 表示一个长度为 n 的数组
// 代码中的 array.length 就等于 n
int[] array = new int[n];
int count = 0;
void insert(int val) {
if (count == array.length) {
int sum = 0;
for (int i = 0; i < array.length; ++i) {
sum = sum + array[i];
}
array[0] = sum;
count = 1;
}
array[count] = val;
++count;
}
这段代码实现了往数组中插入数据的功能,当数组满了之后(count == array.length)利用for循环对数组求和,并清空数组,将求和得到的sum值放在数组的第一个位置,再将新的数据插入,如果数组一开始就有空闲空间,则直接将数据插入数组。
最好情况时间复杂度:最理想的情况下,数组最开始有空闲空间,直接将数据插入数组,时间复杂度为O(1)。
最坏情况时间复杂度:最糟糕的情况下,数组没有空闲空间,先进行遍历求和,再插入数据,时间复杂度为O(n)。
平均情况时间复杂度:n+1种情况的概率是1/n+1,1 * 1/n+1 + 1 * 1/n+1 + 1 * 1/n+1 + 1 * 1/n+1 +… + n * 1/n+1 = O(1)。
其实这里求平均情况时间复杂度的时候不用考虑概率,这个栗子跟之前那个栗子有区别。
- find()函数在极端情况下复杂度才为O(1),但是Insert()函数在大多情况下复杂度都为O(1),只有个别情况下复杂度才为O(n)。
- 对于 insert() 函数来说,O(1) 时间复杂度的插入和 O(n) 时 间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。
咣咣咣咣~~摊还分析法出现了,由此引出摊还分析复杂度。
还看insert()这个栗子,每一次O(n)的插入操作,都会跟着n-1次O(1)的插入操作,所以把耗时多的那次操作均摊到接下来的n-1次耗时少的插入操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是O(1)。
对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时 间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这 一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时 间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复 杂度就等于最好情况时间复杂度。