算法复杂度分析(下)
- 最好&最坏情况时间复杂度
在一个数组中查找目标值x的代码我们可以像下面这样写:
int find(int[] array, int x) {
int pos = -1;
int n = array.length;
for (int i = 0; i < n; i ++) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
目标值x可能出现在数组中的不同位置,也可能没在数组中出现过:
- 如果数组第一个元素就是x,那就不需要遍历接下来的array.length-1个变量了,时间复杂度为O(1);
- 如果数组中不存在x,我们需要把整个数组遍历一次,时间复杂度为O(n);
所以,不同情况下,这段代码的时间复杂度是不一样的。为了表示代码在不同情况下的不同时间复杂度,我们需要引入三个概念:最好时间复杂度、最坏时间复杂度和平均情况复杂度。
顾名思义,最好情况复杂度就是在最理想的情况下,执行这段代码的时间复杂度。就像刚刚我们查找数组中元素x,x正好是数组的第一个元素,这个时候对应的时间复杂度就是最好的情况时间复杂度。
同理,最坏情况时间复杂度就是在糟糕的情况下,执行这段代码的时间复杂度。就像刚刚那个目标值不在数组中的情况,我们需要遍历整个数组,这个时候对应的时间复杂度就是最坏的情况时间复杂度。
- 平均情况时间复杂度
最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,发生的概率并不大。为了更好地表示平均情况下的复杂度,我们引入另一个概念:平均情况时间复杂度。
在上面那段代码中,要查找的目标值x在数组中的情况有n+1中情况:在数组的0~n-1位置和不在数组中。我们把每种情况下,需要遍历的元素个数累加起来,然后再除以n+1,就可以得到需要遍历的元素个数的平均值:
(1+2+3+···+n+n)/(n+1)=n(n+3)/2(n+1)
在大O标记法中,可以忽略掉系数、低阶、常量,所以这个公式简化后之后,得到的平均时间复杂度就是O(n)。
这个结论虽然是正确的,但是其实计算过程稍微有点问题。这里的n+1中情况出现的概率并不是一样的。对于目标值x,要么在数组里,要么不在数组里,为了方便理解,可以假设在数组中和不在数组中的概率都为1/2.另外,要查找的数据出现在0~~n-1中任意位置的概率也是一样的,为1/n。所以,根据概率论知识,要查找的数据出现在0~n-1中任意位置的概率就是1/(2n)。
现在,平均时间复杂度的过程为:
1·1/2n + 2·1/2n + 3·/2n + ··· + n·1/2n + n·1/2 = (3n+1)/4
这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫作加权平均时间复杂度或者期望时间复杂度。