|
04、最好、最坏、平均、均摊时间复杂度
- <font size="6"><font face="微软雅黑" size="5">// 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;
- }</font></font>
复制代码 要查找的变量 x 可能出现在数组的任意位置。如果数组中第一个元素正好是要查找的变量
x,那就不需要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。但如果数组中不存在变
量 x,那我们就需要把整个数组都遍历一遍,时间复杂度就成了 O(n)。所以,不同的情况下,这
段代码的时间复杂度是不一样的。
为了表示代码在不同情况下的不同时间复杂度,我们需要引入三个概念:最好情况时间复杂度、
最坏情况时间复杂度和平均情况时间复杂度。
顾名思义,最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。就像我
们刚刚讲到的,在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,这个时候对应的
时间复杂度就是最好情况时间复杂度。
同理,最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。就像刚举的
那个例子,如果数组中没有要查找的变量 x,我们需要把整个数组都遍历一遍才行,所以这种最
糟糕情况下对应的时间复杂度就是最坏情况时间复杂度。
平均情况时间复杂度
我们都知道,最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,
发生的概率其实并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均
情况时间复杂度,后面我简称为平均时间复杂度。
要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中和不在数组中。我
们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的
元素个数的平均值,
(1+2+3+4+....+n+n)/ (n+1) = n(n+3) / 2(n+1)
时间复杂度的大 O 标记法中,可以省略掉系数、低阶、常量,所以,咱们把刚刚这
个公式简化之后,得到的平均时间复杂度就是 O(n)
这个结论虽然是正确的,但是计算过程稍微有点儿问题。究竟是什么问题呢?我们刚讲的这 n+1
种情况,出现的概率并不是一样的
我们知道,要查找的变量 x,要么在数组里,要么就不在数组里。这两种情况对应的概率统计起
来很麻烦,为了方便你理解,我们假设在数组中与不在数组中的概率都为 1/2。另外,要查找的
数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1/n。所以,根据概率乘法法则,要查找
的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)
因此,前面的推导过程中存在的最大问题就是,没有将各种情况发生的概率考虑进去。如果我们
把每种情况发生的概率也考虑进去,那平均时间复杂度的计算过程就变成了这样:
(1+2+3+....+n) * (1/2n) + n * 1/2 = (3n+1) / 4
这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均
时间复杂度或者期望时间复杂度。
引入概率之后,前面那段代码的加权平均值为 (3n+1)/4。用大 O 表示法来表示,去掉系数和常
量,这段代码的加权平均时间复杂度仍然是 O(n)
只有同一块代码在不同的情况下,
时间复杂度有量级的差距,我们才会使用这三种复杂度表示法来区分。
均摊时间复杂度就是一种特殊的平均时间复杂度,我们没必要花太多精力去区分它
们。你最应该掌握的是它的分析方法,摊还分析。至于分析出来的结果是叫平均还是叫均摊,这
只是个说法,并不重要
|
|