版权声明:本文为博主jmh原创文章,未经博主允许不得转载。 https://blog.csdn.net/jmh1996/article/details/82827579
引言?
在分析算法的时候,我们经常需要分析递归算法的时间复杂度。Master——Theorem 正是用于快速得出递归算法时间复杂度的方法。
Master—Theorem
假设某个递归算法的时间复杂度递归公式为:
T(n)=a∗T(bn)+nd,其中
a>1,b>1,d>0。
这个表达式其实是在表达:将规模为
n的问题转换为a个规模为
bn的子问题,在合并这些子问题的解时需要花费
O(nd)时间。
例如Merge-Sort算法里面,我们将规模为
n的排序问题先转换为 2 个规模为
2n的子问题,然后合并这两个子问题需要花费
O(n)的时间,因此其时间复杂度的递归公式为
T(n)=2T(2n)+O(n)。
那么 Master——Theorem是干嘛的呢?个人觉得,这就是主定理其实提供一种对形式为
T(n)=a∗T(bn)+nd递推公式的快速求解Trick,从接下来的证明我们看到,这个主定理只是对我们的一般化证明过程做了一个小小的总结。
先看主定理的内容:
设某个递归算法的时间复杂度递归公式为:
T(n)=a∗T(bn)+nd,其中
a>1,b>1,d>0。则有:
- 当
a<bd,也就是
logba<d 时,
T(n)=O(nd)
- 当
a=bd,也就是
logba=d 时,
T(n)=O(nlogba∗logn)
- 当
a>bd,也就是
logba>d 时,
T(n)=O(nlogba)
现在,我们来证明一下。
我们知道,这种子问题的划分方式是将规模为n的问题,分解为a个问题规模为
bn的子问题。
对于第0层(最高层),合并子问题需要花费
nd的时间
对于第 1层(第一次划分出出来的子问题),共有
1∗a个子问题,每个子问题合并需要花费
(bn)d,于是在第1层,合并一共需要花费
O(a∗(bn)d)时间,也就是
bda∗nd。
对于第2层。第2层需要对第1层的每个问题都划分为a个更小的子问题。因此,这一层一共有
a∗a个子问题,而这每个子问题合并需要
O((b2n)d)时间。于是这一层合并需要花费:
a2∗(b2n)d=(bda)2∗nd
类似的,我们发现第三层合并需要
a3∗(b3n)d=(bda)3∗nd
···
···
最后第k成的合并需要
ak∗(bkn)d=(bda)k∗nd,
那么这个最大的k是多少呢?我们知道,第k层中每个问题的规模是1,因此我们从下往上推,第k层每个问题规模为1,那么第k-1层 每个问题规模为b,第k-2层每个问题规模为
b2,一直最顶层问题规模为n为止。此时有:
b∗b∗b∗b⋅⋅⋅b=n,即
bk=n,因此
k=logbn
因此,一共有k层。
ok,我们将各层合并消耗的时间累加起来就是T(n)了。
T(n)=nd+(bda)nd+(bda)2nd+(bda)3nd+⋅⋅⋅+(bda)knd
我们把T(n)写更好看一些:
T(n)=(bda)0nd+(bda)1nd+(bda)2nd+(bda)3nd+⋅⋅⋅+(bda)knd
把各项的公因子
nd提出出来:
T(n)=nd((bda)0+(bda)1+(bda)2+(bda)3+⋅⋅⋅+(bda)k)
ok,这看起来就是一个等比数列了哇。为了方便,我们令
q=bda,
那么:
T(n)=nd(q0+q1+q2+q3+⋅⋅⋅+qk)
这就是一个等比数列求和问题,很明显,我们可以使用等比数列求和的方法来搞。
等式两边同时乘以q,得到:
T(n)=nd(q0+q1+q2+q3+⋅⋅⋅+qk) ==>
qT(n)=nd(q1+q1+q2+q3+⋅⋅⋅+qk+1)
上下相减,得到:
(1−q)T(n)=nd(1−qk+1)−−−−−−(式1)
此时我们需要对q分情况讨论:
如果
q=1.
那么会得到
0=0这样的恒等式。此时等比数列错位相减法失效。考虑到q=1,说明这个数列每一项都一样,而第一项为
q0,换句话说,这个等比数列的和其实就是
k(因为有K项)。
于是:
T(n)=nd∗k=nd∗logbn,使用换底公式:
T(n)=nd∗k=nd∗log2blog2n
当b>=2时:
T(n)=nd∗log2blog2n<nd∗log22log2n=O(ndlog2n)
如果
q!=1.
那么
1−q!=0,于是将式1的左右两边同除以
1−q,得到:
T(n)=1−qnd(1−qk+1)
当
q<1时,即
bda<1。当k很大时
qk+1趋于0,所以
1−qk+1趋于1。所以有
T(n)≈nd∗1−q1=c∗nd,也就是说
T(n)=O(nd).
当
q>1时,即
bda>1。当k很大时,
qk+1趋于无穷,
1−qk+1趋于
−qk+1,所以主要项就是
qk,
T(n)≈nd∗qk.
将
k=logbn,q=bda代入。因此有
T(n)≈nd∗qk=nd∗(bda)logbn=nd∗bdlogbnalogbn=nd∗(blogbn)dalogbn=nd∗ndalogbn=alogbn
于是
T(n)=O(alogbn),然而这个好像和定理不太一样,我们需要使用到一个对数的运算性质。
即:
alogbn=nlogba
证明过程很简单,就是使用对数的实际含义。
假设
bx=n,那么
x=logbn,x就是这个对数的含义。
同时假设
by=a,那么
y=logba.
于是,左边
alogbn可以写成:
(by)x;
而右边可以写成
(bx)y.显然,
(by)x=
(bx)y,于是,左边等于右边。
因此
alogbn=nlogba是成立的。
所以
T(n)=O(alogbn),可以写成:
T(n)=O(nlogba).
到此为止,主定理就证明完毕。
我们观察整个证明过程,其实就是对证明过程做了一个小小的归纳总结。在实际应用中,如果忘记主定理,那么直接展开代入实际数值推一遍就可以了。
主定理的使用
主定理可以用于快速得到时间复杂度为递归公式的上界解,下面举几个栗子。
1.
T(n)=3T(2n)+cn
那么:
a=3,b=2,d=1,
log23>1,所以
T(n)=O(nlog23)
2.
T(n)=2T(2n)+cn2
那么:
a=2,b=2,d=2,
log22<2,所以:
T(n)=O(n2)
上面两个,不使用主定理,直接展开也可以得到一样的结果。