本系列文章是以笔记的方式进行记录,主要内容均来源于王争老师的《数据结构与算法之美》
最好、最坏时间复杂度
什么是最好时间复杂度
最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。
什么是最坏时间复杂度
最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。
看例子:
/**
* 在数组中找到指定数字的未知
* @param array 数组
* @param x 需要在数组中查找的数字
* @return 位置
*/
int find(int[] array, int x) {
int i = 0;
int pos = -1;
int n = array.length;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
如果数组中第一个元素正好是要查找的变量 x,那就不需要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。
但如果数组中不存在变量 x,那我们就需要把整个数组都遍历一遍,时间复杂度就成了 O(n)。
所以例子中的最好时间复杂度是O(1),最坏时间复杂度是O(n)。
平均时间复杂度
什么是平均时间复杂度
平均时间复杂度就是,在所有情况取平均值下,执行这段代码的时间复杂度。
看上面的例子,要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中和不在数组中。我们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值,即:
所以省略掉不不要的数后,平均复杂度为O(n)。
上面这个计算其实是有问题的,因为所有情况出现的概率不一样。这里涉及到加权平均值(期望值),我就不展开描述了,有兴趣的同学可以自己了解上链接
均摊时间复杂度
什么是均摊时间复杂度
对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。
// 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;
}
从代码中看出大部分情况下,时间复杂度都为 O(1)。只有个别情况下,复杂度才比较高,为 O(n)。
O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。
所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1)。这就是均摊分析的大致思路。
均摊时间复杂度就是一种特殊的平均时间复杂度,我们没必要花太多精力去区分它们。
本系列其他文章:
《数据结构与算法》目录