前置知识——渐进复杂度记号

本文假设你已经掌握了渐进复杂度的含义,并且熟悉大O记号表示复杂度。除了大O记号外,这里再简单介绍一下其他的复杂度记号。如果你掌握了这些符号,可以跳过本段。

渐进复杂度的记号有Θ\ThetaOOΩ\Omegaooω\omega,主定理用到了前三个,但为了完整性这里全部介绍。

Θ\Theta

c1,c2,n0>0\exists c_1,c_2,n_0 \gt 0,使得nn0\forall n \geq n_0c1g(n)f(n)c2g(n)c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n),则称f(n)=Θ(g(n))f(n) = \Theta(g(n))

Θ\Theta可以精确的表示算法的渐进复杂度,相应地,使用时也要注意严谨性。

用表示大小的符号类比的话,Θ\Theta就相当于==等于号。

OO

c,n0>0\exists c,n_0 \gt 0,使得nn0\forall n \geq n_0f(n)cg(n)f(n) \leq c \cdot g(n),则称f(n)=Θ(g(n))f(n) = \Theta(g(n))

虽然大O记号不如Θ\Theta记号来的精确,但它是讨论复杂度的时候最常用的符号。主要是因为比较好用键盘输入,并且它指出了我们关心的运行时间上界(尤其是当一些算法的下界不太好计算得出时)

用表示大小的符号类比的话,OO就相当于\leq小于等于号。

Ω\Omega

c,n0>0\exists c,n_0 \gt 0,使得nn0\forall n \geq n_0f(n)cg(n)f(n) \geq c \cdot g(n),则称f(n)=Ω(g(n))f(n) = \Omega(g(n))

用表示大小的符号类比的话,OO就相当于\geq大于等于号。

oo

c>0,  n0>0\forall c>0,\;\exists n_0>0,使得 nn0\forall n \geq n_0f(n)<cg(n)f(n)<c\cdot g(n),则称f(n)=o(g(n))f(n) = o(g(n))

用表示大小的符号类比的话,oo就相当于<<小于号。

ω\omega

c>0,  n0>0\forall c>0,\;\exists n_0>0,使得 nn0\forall n \geq n_0f(n)>cg(n)f(n)>c\cdot g(n),则称f(n)=ω(g(n))f(n) = \omega(g(n))

用表示大小的符号类比的话,ω\omega就相当于>>大于号。

整理

为了便于记忆,可以整理成下面的表格。

复杂度记号 帮助记忆
Θ\Theta ==
OO \leq
Ω\Omega \geq
oo <\lt
ω\omega >\gt

介绍

主定理(Master Theorem)是用于分析分治算法复杂度的常用方法,给定算法运行时间与输入规模的函数的递推关系式,就可以计算得到算法的渐进时间复杂度。

T(n)=aT(nb)+f(n)T(n) = aT(\frac{n}{b}) + f(n)其中a1,b>1a \geq 1, b \gt 1

其中:

  • nn是问题规模
  • aa是分治的子问题数量
  • nb\frac{n}{b}是每个子问题的规模(b>1)
  • f(n)f(n)是分治算法需要的额外时间(即拆分成子问题的用时,以及将子问题的答案合并成最终结果的用时)

有:

T(x)={Θ(nlogba),if ε>0,f(n)=O(nlogb(a)ε)Θ(nlogbalogε+1n),if ε0,f(n)=Θ(nlogbalogεn)Θ(f(n)),if ε>0,f(n)=Ω(nlogb(a)+ε)meanwhile c<1, for sufficiently large nf(nb)cf(n)T(x)=\begin{cases} \Theta(n^{\log _b a}), & \text{if $\exists \varepsilon \gt 0, f(n)=O(n^{\log _b (a) - \varepsilon})$}\\ \Theta(n^{\log _b a}\log ^{\varepsilon + 1} n), & \text{if $\exists \varepsilon \geq 0, f(n)=\Theta(n^{\log _b a}\log ^\varepsilon n)$}\\ \Theta(f(n)), & \text{if $\exists \varepsilon \gt 0, f(n)=\Omega(n^{\log _b (a) + \varepsilon})$}\\ &\text{meanwhile $\exists c \lt 1$, for sufficiently large $n$, $f(\frac{n}{b}) \leq cf(n)$} \end{cases}

为了方便理解和记忆,也可以不严谨地写成这样

T(x)={Θ(nlogba),if f(n)<nlogbaΘ(f(n)logn),if f(n)=nlogbalogkn(k0)Θ(f(n)),if f(n)>nlogbaT(x) = \begin{cases} \Theta(n^{\log _b a}), & \text{if $f(n) \lt n^{\log _b a}$}\\ \Theta(f(n) \log n), & \text{if $f(n) = n^{\log _b a} \log ^k n \quad (k \geq 0)$}\\ \Theta(f(n)), & \text{if $f(n) \gt n^{\log _b a}$}\\ \end{cases}

注意,这里的<\lt==>\gt并不是字面上的意思,只是为了方便理解记忆。真正的含义请看原式子

也就是说,只要比较f(n)f(n)nlogban^{\log _b a}的大小(多项式比大小)。哪一个大,T(n)T(n)就等于Θ(哪个)\Theta(\text{哪个}),如果一样大,或者相差logkn\log ^k n,那么T(n)就等于Θ(f(n)logn)\Theta(f(n)\log n),是不是很简单。

如果只考虑f(n)=ndf(n)=n^d的情况,那么就更简单了

T(x)={Θ(ndlogn),if d=logbaΘ(nmax{d,logba}),otherwiseT(x) = \begin{cases} \Theta(n^d \log n), & \text{if $d = \log _b a$}\\ \Theta(n^{\max \{ d, log _b a \}}), & \text{otherwise}\\ \end{cases}

实例

这几个例子的复杂度其实不使用主定理也能很容易看出来,这里只是用做例子,方便理解主定理具体是怎么应用的。对于更复杂问题的分治,相对来说就没那么容易看出来了,这时候主定理也就能派上用场了。

二分查找

在二分查找的例子中,nn表示查找范围的大小,每一次执行二分查找时,查找范围减半,b=2b=2,子问题数量仍为1,a=1a=1,每一次二分用时是O(1)O(1)

nlogba=1n^{\log _b a} = 1与f(n)同阶,则T(n)=Θ(f(n)logn)=Θ(logn)T(n)=\Theta(f(n)\log n)=\Theta(\log n)

遍历二叉树

a=2b=2f(n)=O(1)nlogba=n\begin{align*} a &= 2\\ b &= 2\\ f(n) &= O(1)\\ n^{\log _b a} &= n \end{align*}

nnf(n)f(n)大,因此T(n)=f(n)=Θ(n)T(n)=f(n)=\Theta(n)

归并排序

归并排序中,a=b=2a=b=2,而f(n)f(n)等于多少呢?我们考察归并的过程,两个已经排序好的数组合并成一个时,比较操作的次数是 O(n)O(n) 的,而将元素转移到临时数组再转移回来所用的时间也是O(n)O(n)的,显然f(n)=O(n)f(n)=O(n)

nlogba=nn^{\log _b a} = nf(n)f(n)同阶,则T(n)=nlognT(n)=n \log n