复杂度分析

朴素分析

  • 直接暴力统计计算次数。

摊还分析

聚合分析

  • 核心思想:算出总复杂度然后均摊。

  • 例:

    • 1.“栈”:维护一个栈,支持栈顶插入和一次弹出所有元素。

      • 显然 \(n\) 次操作至多 \(n\) 个点插入,从而最多有 \(n\) 个点弹出。

      • 总复杂度 \(O(2n)\),均摊复杂度 \(O(2)\)

    • 2.\(vector\) 的复杂度分析(仅考虑队尾插入和申请空间的操作)。

      • 按一般的算法,队尾插入 \(O(1)\),申请空间 \(O(size)\)

      • 显然至多申请 \(2^{\lceil log_2\ n\rceil}\)的空间,可以向上取到 \(O(2n)\)。然后插入 \(O(n)\),均摊复杂度 \(O(3)\)

核算法

  • 核心思想:在复杂度较低的操作上“预支”复杂度以防止负债。

  • 注意,低复杂度操作的复杂度并没有真的升高,这种变换只是为了方便统计复杂度。

  • 例:

    • 1.\(vector\) 的复杂度分析(仅考虑队尾插入和申请空间的操作)。
      • 按一般的算法,队尾插入 \(O(1)\),申请空间 \(O(size)\)

      • 我们考虑把队尾插入的复杂度上升到 \(O(3)\)

      • 从而上次开出的新点(恰为 \(\dfrac{size}{2}\) 个)每个都有 \(2\) 冗余复杂度。均摊到 \(size\) 个点,冗余为 \(1\),恰好够申请新一倍的空间。

      • 即,总复杂度 \(O(3n)\),均摊复杂度 \(O(3)\)

      • 有趣的是,通过聚合分析算出的复杂度是往往达不到的,而核算法算出的复杂度是高度近似的(这里仅从计算原理来讲)。

      • 然而如果我们把 \(n\) 取成 \(2\) 的整数次方的话,聚合分析可以得到 \(O(2)\),核算法仍然是 \(O(3)\)(最后存的很大一部分复杂度没有用到)。

      • 这就启示我们,所有摊还分析本质上都是近似复杂度分析,并且常常向上取来保证不炸,从而精度未必非常良好。想要取得尽量精确的复杂度,需要选用正确的摊还方法。

势能分析

  • 核心思想:

    • 1.记当前状态为 \(D\),初始状态(通常为空)为 \(D_0\)

    • 2.定义势函数 \(\phi(D)\),使得总有 \(\phi(D)\geqslant \phi(D_0)\)

    • 3.记每种操作的实际代价为 \(c\),定义其摊还代价为 \(c'=c+\phi(now)-\phi(pre)\)。从而有 \(\dfrac{\sum\limits_{i=1}^n c'}{n}\) 给出了一种近似(一般略大于实际)的均摊复杂度。

  • 例:

    • 1.\(KMP\) 的复杂度分析。参见“字符串”。

    • 2.\(splay\) 的复杂度分析。

主定理

  • 其实不一定应该放在这里...

  • 主定理给出了一种求解递归算法或分治算法的时间复杂度的方法。其具体形式如下:

    • 定义 \(T(n)\) 为规模为 \(n\) 的问题在递归算法下的复杂度,且有 \(T(n)=aT(\dfrac{n}{b})+f(n)\),其中 \(f(n)\) 为规模为 \(n\) 的问题在分治后的合并复杂度。

    • \(f(n)=O(n^c),c'=\log_ba\)。则我们有:

    \[T(n)=\begin{cases}f(n)=O(n^c),c>c'\\f(n)\times\log_bn=O(n^c\times\log_bn),c=c'\\ O(n^{c'}),c<c'\end{cases} \]

  • 可以简单地这么记忆:两个元素,合并复杂度与 \(n^{\log_b a}\);有主导看主导,没主导带个 \(\log_b a\)

posted @ 2023-01-15 10:00  未欣  阅读(21)  评论(0编辑  收藏  举报