这是我学习《数据结构与算法之美》的第04课:复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度
课程原文链接可点击此处.
当一段循环代码中有特殊的情况会提前退出循环的时候,代码的时间复杂度就可以算得再细一些,比如这段代码:
// 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;
}
最好、最坏情况时间复杂度
最好和最坏时间复杂度,其实是给出了一段代码运算时间的范围、上下限,这个数据在某些情况下可能会比较有用,比如系统的响应时间上限这类情况,可以提前预估最坏情况下的风险。
举例:给领导汇报的时候,经常会说我这个代码最快0.01毫秒就能算出来,最慢也只需要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;
break;
}
}
return pos;
}
这段代码中,x 是唯一的变量,x 在 array 中的位置决定了这段代码啥时候结束。x 在 array 中的位置有 n 种情况,还有1种情况是 x 不在 array 中,总共 n+1 种可能。假设这 n+1 种可能出现的概率是平均的,那么这段代码种 for 循环总共运行的次数为 (1+2+3+…+n+n)/(n+1) = (n2+3n)/2(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;
}
上面这段代码,假设 val 插入到 array 的位置概率相等,有 n+1种可能,平均时间复杂度是 O(1);但这代码有个规律,它的时间复杂度是 O(1) + O(1) + …n个O(1) + O(n) + O(1) + O(1) + …n个O(1) + O(n) 这么一串有规律的复杂度组成的,可以换种思路,不考虑平均时间复杂度了,而是将每个O(n)的时间平摊到前面的 n 个 O(1) 身上,是不是就都变成常量级的了?这就是均摊时间复杂度的思路。
均摊时间复杂度在特定的场景才能均摊。
思考题
// 全局变量,大小为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;
}
上面代码中,add 方法的最好时间复杂度为 O(1),最坏时间复杂度为 O(n),平均时间复杂度为 O(1),这个是可以平摊的,平摊时间复杂度为O(1)。
参考链接
无