再谈复杂度分析
复杂度分析
主定理
函数 \(f(n)\) 表示数据规模为 \(n\) 时的计算量。
渐进符号的定义:
-
渐进符号表示了函数增长趋势的重要部分,而忽略增长较慢的部分以及各项系数。大白话就是表示了函数的规模。
-
\(f(n)=\Theta(g(n))\),表示存在 \(c_1,c_2,n_0>0\) 使得对于所有 \(n\ge n_0\),有 \(c_1\times g(n)\le f(n)\le c_2\times g(n)\)。
-
\(f(n)=O(g(n))\),表示 \(0\le f(n)\le c_1\times g(n)\),此处应有 \(c_1,n_0\) 但省略,下同。
-
\(f(n)=\Omega(g(n))\),表示 \(0\le c_1\times g(n)\le f(n)\)。
-
还有 \(o\) 和 \(\omega\) 分别把上面的等号去掉,用于对无穷的分析,不展开。
记忆:\(\Theta\) 看做等于,\(O\) 看做小于等于,\(\Omega\) 看做大于等于。
实际书写的时候常常只用 \(O\),因为我们关心程序的最坏运行效率。
我们认为渐进符号相当于一个集合,\(f(n)=O(g(n))\) 是说 \(f(n)\in O(g(n))\)。那么渐进符号的运算就是集合中选数运算后的结果构成的集合,其也能用渐进符号表示,那么有如下性质:
- \(O(f_1(n))+O(f_2(n))=O(\max(f_1(n),f_2(n)))\)。
- \(O(f_1(n))\times O(f_2(n))=O(f_1(n)\times f_2(n))\)。
- \(\log _an=O(\log _2n)\),因此我们一般不写底数。
需要指出的是两个相等的渐进符号做减法或除法并不等于 \(0\) 或 \(1\)。
主定理的表达式
对于递推式
那么
这里有一种更易于理解的形式,若 \(f(n)=O(n^d \log^k n)\),则:
例子
- \(T(n)=2T(n/2)+O(n^2)\)。
解:\(d=2,\log _ba=1\),属于情况一,得 \(T(n)=O(n^2)\)。 - \(T(n)=2T(n/2)+O(n \log n)\)。
解:\(d=1,\log _ba=1\),属于情况二,得 \(T(n)=O(n\log ^2n)\)。 - \(T(n)=2T(n/2)+O(\sqrt n)\)。
解:\(d=0.5,\log _ba=1\),属于情况三,得 \(T(n)=O(n)\)。 - \(T(n)=2T(\sqrt n)+O(\log n)\)。
解:设 \(2^m=n\),原式可以写成 \(T(2^m)=2T(2^{m/2})+O(m)\),设 \(S(m)=T(2^m)\) 则 \(S(m)=2S(m/2)+O(m)\)。
利用主定理得 \(S(m)=O(m\log m)\),代入 \(m=\log n\) 得 \(T(n)=O(\log n\log\log n)\)。
势能分析
定义状态 \(S\) 为数据结构的状态,其包含数据结构所有信息。
定义数据结构的势能 \(\Phi(S)\),要满足对于初态 \(S_0\) 以及任意 \(S\) 满足 \(\Phi(S)\ge \Phi(S_0)\)。
定义 \(c_i\) 表示第 \(i\) 次操作的实际计算量,\(S_i\) 表示第 \(i\) 次操作后数据结构的状态,\(p_i\) 表示第 \(i\) 次操作的均摊成本。其中
那么 \(m\) 次操作的总实际成本为
因为 \(\Phi(S_m)\ge \Phi(S_0)\),所以得到总实际成本小于等于总均摊成本。
因此总均摊成本的上界就是总实际成本的一个上界。
这意味着,我们可以给数据结构定义一个满足条件的合适的势能,如果计算出总均摊成本等于 \(O(T(n))\),那么就证明了总实际成本等于 \(O(T(n))\)。
例子:vector 扩容的分析
我们知道 vector 的扩容规则是,记录一个当前长度 \(n\) 和当前容量 \(m\),初始 \(m=1\)。插入时若 \(n<m\) 那么直接插入这个元素;否则先扩容:\(m\gets m\times 2\),再插入元素。
我们定义势能为 \(2n-m\),则初态势能为 \(-2\),而显然任意态势能都 \(\ge -2\),符合定义。
- 操作一,直接插入,实际成本为 \(1\),则势能显然增加 \(1\),于是此时 \(p_i=2\)。
- 操作二,扩容并插入,实际成本为 \(m+1\),而势能增加了 \((2(n+1)-2m)-(2n-m)=2-m\),于是此时 \(p_i=3\)。
可见任意次操作,每次操作的均摊成本是 \(O(1)\),总实际成本就是与操作次数线性相关。
需要指出的问题
-
为什么插入操作成本要看做 \(1\),扩容的成本要看做 \(m\)?
答:这并不是说扩容的成本恰好是插入的 \(m\) 倍,因为他们本质不同所以实际的运算量不可比,但是规模可以比。这里的 \(1,m\) 指的操作的规模。插入操作与当前容量 \(m\) 无关于是成本规模为 \(1\),而扩容操作与 \(m\) 有关成本规模为 \(m\)。
-
为什么实际成本与势能可以相互加减呢?
答:实际成本的和是一个表示规模的量,而上文已经说过均摊成本是大于等于实际成本的,因此可以用均摊成本的规模表示实际规模。
-
如果把实际成本多乘一个系数 \(k\) 会怎么样?
答:多乘一个 \(k\),那么只需要换一个更合适的势能函数即可。

浙公网安备 33010602011771号