一.复杂度的定义.
复杂度:复杂度是衡量一个代码质量的重要指标,主要分为时间复杂度与空间复杂度,其中又以时间复杂度更为重要.
复杂度记号:一个算法XXX的复杂度上界O(X)O(X)O(X)定义为XXX的运行总量多项式只保留最高次项并去掉常数后的值,例如:
O(3n2+5n+7)=O(n2)O(78n+10log3.4n)=O(n)O(5n2log3.53(100n))=O(n2log3n)O(2n+2+8n7)=O(2n)
O(3n^{2}+5n+7)=O(n^{2})\\
O(7\sqrt{8n}+10\log_{3.4} n)=O(\sqrt{n})\\
O(5n^{2}\log_{3.5}^{3}(100n))=O(n^{2}\log^{3} n)\\
O(2^{n+2}+8n^{7})=O(2^{n})
O(3n2+5n+7)=O(n2)O(78n+10log3.4n)=O(n)O(5n2log3.53(100n))=O(n2log3n)O(2n+2+8n7)=O(2n)
当然有些时候常数对代码的效率影响也很大,但在复杂度计算中不予考虑.
其实记号OOO表示的只是一个上界,也就是说一个算法的复杂度为O(n2)O(n^2)O(n2),那么它的复杂度也可以被写为O(n3)O(n^3)O(n3).
再来一些实例解释一下吧,比如说下面这串代码:
int ans=0;
for (int i=1;i<=n;i+=5)
for (int j=n;j>=10;--j)
for (int k=1;k<=n;k+=2) ++ans;
这串代码的时间复杂度就是O(n3)O(n^3)O(n3).
在比如说下面这串代码:
int ans=0;
for (int i=1;i<=n;i<<=1)
for (int j=2;j<1<<i;++j)
for (int k=4;k*k<=n;++k) ++ans;
这串代码的时间复杂度就是O(2nnlogn)O(2^{n}\sqrt{n}\log n)O(2nnlogn).
除此之外,复杂度还有记号例如Ω(n)\Omega(n)Ω(n)和Θ(n)\Theta(n)Θ(n),其中Ω\OmegaΩ表示下界,Θ\ThetaΘ表示上下都有界.
二.积分估计计算时间复杂度.
若对于某一个和式:
∑i=1nf(i)
\sum_{i=1}^{n}f(i)
i=1∑nf(i)
其中f(x)f(x)f(x)是一个多项式,但这个东西并不好估计它的具体值,我们该如何计算呢?
有一个非常简单的方法,直接将这个和式看成积分的离散表示,然后直接用积分算复杂度.
由于复杂度计算的只是大概值,所以这样算是没有关系的.
也就是说:
O(∑i=1nf(i))=O(∫1nf(i)di)
O\left(\sum_{i=1}^{n}f(i)\right)=O\left(\int_{1}^{n}f(i)\mathrm{d}i\right)
O(i=1∑nf(i))=O(∫1nf(i)di)
一个经典的例子是计算如下和式在复杂度表示中的值:
O(n∑i=1n1i)=O(n∫i=1n1idi)=O(nlogn)
O\left(n\sum_{i=1}^{n}\frac{1}{i}\right)=O\left(n\int_{i=1}^{n}\frac{1}{i}\mathrm{d}i\right)=O(n\log n)
O(ni=1∑ni1)=O(n∫i=1ni1di)=O(nlogn)
三.主定理.
对于一个递归函数:
T(n)=aT(nb)+f(n)
T(n)=aT(\frac{n}{b})+f(n)
T(n)=aT(bn)+f(n)
如何计算O(T(n))O(T(n))O(T(n))呢?
主定理:对于T(n)=aT(nb)+f(n)T(n)=aT(\frac{n}{b})+f(n)T(n)=aT(bn)+f(n),则有:
O(T(n))={O(nlogba)∃k>0⇒f(n)=O(nlogba−k)O(nlogbalogk+1n)∃k>0⇒f(n)=O(nlogbalogkn)O(f(n))∃k>0⇒f(n)=Ω(nlogba+k)
O(T(n))=
\left\{\begin{matrix}
O(n^{\log_{b}a})&\exists k>0\Rightarrow f(n)=O(n^{\log_{b}a-k})\\
O(n^{\log_{b}a}\log^{k+1}n)&\exists k>0\Rightarrow f(n)=O(n^{\log_{b}a}\log^{k}n)\\
O(f(n))&\exists k>0\Rightarrow f(n)=\Omega(n^{\log_{b}a+k})
\end{matrix}\right.
O(T(n))=⎩⎨⎧O(nlogba)O(nlogbalogk+1n)O(f(n))∃k>0⇒f(n)=O(nlogba−k)∃k>0⇒f(n)=O(nlogbalogkn)∃k>0⇒f(n)=Ω(nlogba+k)
有了主定理我们就可以算很多分治算法的复杂度了,例如:
T(n)=2T(n2)+O(1)⇒O(T(n))=O(n)T(n)=T(n2)+O(1)⇒O(T(n))=O(logn)T(n)=2T(n2)+O(nlogn)⇒O(T(n))=O(nlog2n)T(n)=2T(n2)+O(n2)⇒O(T(n))=O(n2)
T(n)=2T(\frac{n}{2})+O(1)\Rightarrow O(T(n))=O(n)\\
T(n)=T(\frac{n}{2})+O(1)\Rightarrow O(T(n))=O(\log n)\\
T(n)=2T(\frac{n}{2})+O(n\log n)\Rightarrow O(T(n))=O(n\log^2 n)\\
T(n)=2T(\frac{n}{2})+O(n^2)\Rightarrow O(T(n))=O(n^2)
T(n)=2T(2n)+O(1)⇒O(T(n))=O(n)T(n)=T(2n)+O(1)⇒O(T(n))=O(logn)T(n)=2T(2n)+O(nlogn)⇒O(T(n))=O(nlog2n)T(n)=2T(2n)+O(n2)⇒O(T(n))=O(n2)
四.均摊分析.
有些时候,一个算法有nnn步,处理每一步的时候复杂度可能达到O(n)O(n)O(n),但其总复杂度却可以保证O(n)O(n)O(n),是不是非常神奇?
是的,确实有这种算法,而且还有不少,例如KMP就是其中最经典的一种.
那么它的复杂度是怎么保证的呢?有一个叫均摊复杂度方法可以证明.
例如KMP的时间复杂度分析过程:我们注意到KMP的每执行一次j=next[j]j=next[j]j=next[j]都会使得jjj至少−1-1−1,而总共nnn步中jjj每步最多只能+1+1+1,且jjj无论何时都是>0>0>0的,所以它的复杂度是O(n)O(n)O(n)的.
这就是一种均摊时间复杂度的方法.
本文深入讲解算法复杂度的概念,包括时间复杂度与空间复杂度,介绍复杂度记号如O、Ω、Θ,并通过实例解释如何计算复杂度,涉及积分估计、主定理及均摊分析等方法。

被折叠的 条评论
为什么被折叠?



