前言
上一篇文章基础的时间复杂度和空间复杂度分析讲到了一些基础概念,比如big O,big Omega,big Theta等的定义,和分析时间复杂度的两大法则。
因为最好时间复杂度(
B
(
n
)
B(n)
B(n))和最坏时间复杂度(
W
(
n
)
W(n)
W(n))的分析比较简单,所以我们直接跳过。在本篇文章中将着重讲解平均时间复杂度和均摊时间复杂度的区别。
平均时间复杂度(A(n))
通过概率论中学过的一些方法来对所有的情况进行出现概率上的分析,从而计算时间复杂度。比如我们举一个选择扛旗人的例子:现在星灵还缺一个扛旗人扛起神族复兴的希望,我们将在一列随机排序的候选人中选出上天指定的候选人,并返回他在候选人中的位置。
int choose_the_hope(int super_star){
int p_arr = [1,8,2,3,7,4,5,9,6];
int length = 9;
for(int i = 1; i < length; i++){
if(p_arr[i] == super_star){
return i; //返回该数组index
}
}
return -1; // 如果找不到人,那也没办法咯
}
我们不能想当然直接把所有情况所需要的次数都加起来然后除以总情况(
1
+
2
+
.
.
.
+
n
+
n
n
+
1
\frac{1+2+...+n+n}{n+1}
n+11+2+...+n+n,一个n是在扛旗人在最后一个位置,另一个n是扛旗人根本就不在列表内,全部情况加起来是n+1种),因为这样算是默认了所有的情况发生的概率都是一样的,而事实并非如此。
我们要分两种情况讨论,假设扛旗人在列表内和扛旗人不在列表内的概率都是
1
2
\frac{1}{2}
21,我们进一步分析,如果扛旗人在列表内,那么所有情况出现的概率都是
1
n
\frac{1}{n}
n1;如果扛旗人不在列表内,那么就一定会遍历一遍列表。
由此,我们的计算步骤应该是
1
⋅
1
2
n
+
2
⋅
1
2
n
+
.
.
.
+
n
⋅
1
2
n
+
n
⋅
1
2
=
3
n
+
1
4
∈
O
(
n
)
1 \cdot \frac{1}{2n} + 2 \cdot \frac{1}{2n} + ... + n \cdot \frac{1}{2n} + n \cdot \frac{1}{2} = \frac{3n+1}{4} \in O(n)
1⋅2n1+2⋅2n1+...+n⋅2n1+n⋅21=43n+1∈O(n)
随机算法
上面这个例子中我们已知了该列表是随机排序的,但真实情况其实不一定如此。 所以我们不能保证我们每次遇到的都不是最坏情况(上面案例中W(n)和A(n)一样都是O(n),但实际情况中可能最坏情况比平均情况差多了),于是每次我们都需要用随机算法将顺序打乱,以确保我们可以得到一个比较适中的结果。
均摊时间复杂度
个人感觉均摊时间复杂度是一个有点“虚”的概念,它不像上面的平均时间复杂度一样可以通过概率论的一些公式得到,而是一种估算或者说再分配的过程。我们可以用一个简单例子来类比这种思想:假设有一台自动贩卖机可以装n瓶快乐肥宅水,每次投币都会有一瓶水出来,但如果里头没饮料了,就得等一段时间的重新装货,且一个单位时间只能装入一瓶饮料,那么每次拿水出来大概都要花多久呢?
我们可以这样分析,除了拿第n+1瓶饮料的人要等n个单位时间( O ( n ) O(n) O(n)),其他人都只需要等1个单位时间( O ( 1 ) O(1) O(1))就可以拿到饮料,所以我们只要将拿第n+1瓶饮料的人的时间均摊到所有人身上,就可以得到一个时间了,这样来看,其实每个人都只要花 O ( 1 ) O(1) O(1)的时间。
当出现在99.99%的情况下时间复杂度都挺低的,但总有那0.01%的可能性时间复杂度非常高这样的场景时,均摊时间复杂度就可以上线使用了!