增长函数(Growth Functions)概述¶
在算法分析中,增长函数(也称 asymptotic growth)用于描述一个算法随输入规模 (n) 增大时所需的资源(时间、空间)如何变化。
它们的核心思想是 忽略常数因子和低阶项,只关注最“显著”的那一部分——因为当 (n) 足够大时,这部分支配了整体的资源消耗。
下面从 数学定义 → 记号体系 → 典型例子 → 对比技巧 → 常见误区 → 实践使用 四个层面,系统地介绍增长函数。
1️⃣ 形式化定义¶
设 $(f, g : \mathbb{N} \rightarrow \mathbb{R}_{\ge 0}) $为非负函数(常表示算法的运行次数或所占空间)。
我们用 渐近记号(asymptotic notation)来刻画它们的相对增长速度。
| 记号 | 形式化定义 | 直观解释 |
|---|---|---|
| 大 O (\displaystyle O(g(n))) | (f(n) \in O(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le f(n) \le c\cdot g(n)). | 上界:(f) 最多和 (g) 同阶(常数因子无关紧要)。 |
| 大 Ω (\displaystyle \Omega(g(n))) | (f(n) \in \Omega(g(n)) \iff \exists\,c>0,\,n_0) 使得 (\forall n \ge n_0,\; 0 \le c\cdot g(n) \le f(n)). | 下界:(f) 至少和 (g) 同阶。 |
| 大 Θ (\displaystyle \Theta(g(n))) | (f(n) \in \Theta(g(n)) \iff f(n) \in O(g(n))) 且 (f(n) \in \Omega(g(n))). | 紧确界:(f) 与 (g) 同阶,常数因子可忽略。 |
| 小 o (\displaystyle o(g(n))) | (\displaystyle f(n) \in o(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le f(n) < c\cdot g(n).) | 严格上界:(f) 的增长速度严格低于 (g)。 |
| 小 ω (\displaystyle \omega(g(n))) | (\displaystyle f(n) \in \omega(g(n)) \iff \forall c>0,\;\exists n_0:\forall n\ge n_0,\;0\le c\cdot g(n) < f(n).) | 严格下界:(f) 的增长速度严格高于 (g)。 |
等价的极限公式(极限判别法)
[
\begin{aligned}
f \in O(g) &\iff \limsup_{n\to\infty}\frac{f(n)}{g(n)} < \infty,\
f \in \Omega(g) &\iff \liminf_{n\to\infty}\frac{f(n)}{g(n)} > 0,\
f \in \Theta(g) &\iff 0<\lim_{n\to\infty}\frac{f(n)}{g(n)}<\infty,\
f \in o(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = 0,\
f \in \omega(g) &\iff \lim_{n\to\infty}\frac{f(n)}{g(n)} = \infty.
\end{aligned}
]
2️⃣ 常见函数族(Growth Hierarchy)¶
| 类别 | 典型函数 | 记号示例 | 备注 |
|---|---|---|---|
| 常数 | (1) | (\Theta(1)) | 与输入规模无关 |
| 对数 | (\log n, \log_{2} n, \ln n) | (\Theta(\log n)) | 底数不同只差常数因子 |
| 线性 | (n) | (\Theta(n)) | 常见遍历、线性搜索 |
| 线性对数 | (n\log n) | (\Theta(n\log n)) | 归并排序、堆排序的复杂度 |
| 多项式 | (n^{k})((k) 为常数) | (\Theta(n^{k})) | (k=2) → (\Theta(n^{2}))(插入/冒泡) |
| 指数 | (c^{\,n})((c>1)) | (\Theta(c^{n})) | 递归暴力搜索、背包 2‑划分 |
| 超指数/阶乘 | (n!) | (\Theta(n!)) | 旅行商(TSP)暴力求解 |
| 双指数 | (2^{\,2^{n}}) | (\Theta(2^{2^{n}})) | 极少数理论构造 |
| 多项式乘以指数 | (n^{k}c^{n}) | (\Theta(n^{k}c^{n})) | 部分 DP 状态压缩的复杂度 |
增长顺序(从慢到快)(省略常数因子):
[
1 \;<\; \log n \;<\; n \;<\; n\log n \;<\; n^{2} \;<\; n^{3} \;<\; 2^{\,\log n}=n^{\log 2} \;<\; 2^{n} \;<\; n! \;<\; 2^{\,2^{n}} \;<\; \dots
]
重要事实
- (\log^{k} n = o(n^{\epsilon})) 对任意常数 (k) 与 (\epsilon>0)。
- (n^{c} = o(c^{n})) 对任何常数 (c>1)。
- (\log n = o(n^{\epsilon})) —— 这解释了 “对数时间” 远优于 “线性时间”。
3️⃣ 如何比较两个增长函数¶
3.1 直接使用极限(L’Hôpital、级数)¶
例子 1:比较 (f(n)=n^{2}) 与 (g(n)=n\log n)。
[
\lim_{n\to\infty}\frac{n^{2}}{n\log n}= \lim_{n\to\infty}\frac{n}{\log n}= \infty.
]
因此 (n^{2}\in \omega(n\log n)),即 (n\log n = o(n^{2}))。例子 2:比较 (f(n)=2^{n}) 与 (g(n)=n!)。
利用斯特林公式 (n! \sim \sqrt{2\pi n}\,(n/e)^{n}),则
[
\frac{2^{n}}{n!}\approx \frac{2^{n}}{(n/e)^{n}}= \left(\frac{2e}{n}\right)^{n}\xrightarrow{n\to\infty}0,
]
所以 (2^{n}=o(n!))。
3.2 递归树/主定理的经验法则¶
- 多项式 vs. 指数:若递归树的分支因子是常数(如 2),则最终复杂度往往是指数,而如果每层的工作量是 多项式,则整体仍是 多项式(主定理 ①‑③)。
- 对数乘常数:任何 (c\cdot\log n) 都在 (\Theta(\log n)) 里,因为常数因子被忽略。
3.3 摊销分析(Amortized)¶
- 例子:向动态数组(如
vector)中逐次push_back。 - 每次扩容成本是 (O(n)),但均摊后每次操作为 (O(1)),故总体为 (\Theta(n)),单次操作的 增长函数 为 (O(1))(均摊视角)。
3.4 规则记忆技巧(助记口诀)¶
| 规则 | 解释 | 示例 |
|---|---|---|
| 常数 < 对数 < 线性 < 线性对数 < 多项式 < 指数 < 阶乘 | 只看 最高阶,忽略常数 | (5n^2 + 3n + 12 = \Theta(n^2)) |
| 底数不同的对数等价 | (\log_{a} n = \frac{1}{\log a}\log n) | (\log_{10} n = \Theta(\log n)) |
| 指数函数的底数不同也等价 | (a^{n} = \Theta(b^{n}))((a,b>1)) | (2^{n} = \Theta(3^{n}))(仅常数因素差) |
| 多项式乘指数 = 指数 | (n^{k}c^{n} = \Theta(c^{n})) | (n^{5}2^{n} = \Theta(2^{n})) |
| 对数的多项式次方仍是对数 | ((\log n)^{k}=o(n^{\epsilon})) | ((\log n)^{10} = o(\sqrt{n})) |
4️⃣ 典型增长函数实例与常见算法对应¶
| 增长函数 | 典型算法/问题 | 关键技术 |
|---|---|---|
| (\Theta(1)) | 哈希表的 查找/插入(均摊)、数组随机访问 | 均摊分析、哈希冲突概率 |
| (\Theta(\log n)) | 二分搜索、平衡二叉搜索树(红黑树、AVL) | 递归/迭代的分治结构 |
| (\Theta(n)) | 线性遍历、计数排序的计数阶段、链表遍历 | 直接扫描 |
| (\Theta(n\log n)) | 归并排序、堆排序、快速排序(平均)、线性时间排序的 基数排序(基数常数) | 分治+合并、堆操作 |
| (\Theta(n^{2})) | 冒泡/插入/选择排序、所有 (O(n^{2})) 的动态规划(如 LCS 再无优化) | 双层循环、DP 表填充 |
| (\Theta(n^{3})) | Floyd‑Warshall、三层嵌套循环的矩阵乘法(朴素) | 三重循环 |
| (\Theta(2^{n})) | 旅行商(暴力 TSP)、子集枚举、递归背包(指数) | 位掩码、递归分支 |
| (\Theta(n\,2^{n})) | DP on subsets(如 Hamiltonian Path DP) | 状态压缩 DP |
| (\Theta(n!)) | 全排列生成、全排列搜索(TSP 朴素) | 全排列递归 |
| (\Theta(\log^{k} n)) | 递归树高度为 (\log^{2} n) 的 分治 + 合并(如分块 & 线段树合并) | 多层分治 |
| (\Theta(\sqrt{n})) | 区间 sqrt decomposition(块大小 (\sqrt{n})) | 块分技术 |
| (\Theta(n^{\log_{b} a})) | 主定理中 (T(n)=a\,T(n/b)+f(n)) 的平衡情况 | 主定理(Case 1) |
5️⃣ 常见误区与如何避免¶
| 误区 | 说明 | 正确做法 |
|---|---|---|
| 把常数因子当成阶层 | 认为 “5n 比 n 更差”,忽视渐进意义。 |
在 Θ 表示法里,系数被视为 O(1),只关注最高阶项。 |
| 低阶项能忽略但不一定 | 在 实际运行 中,若 (n) 较小,n^2 可能慢于 100n. |
把 理论 与 实验 结合:对目标规模做基准测试。 |
| 把对数底看成意义重大 | 误以为 log_2 n 与 log_10 n 差别很大。 |
使用 (\log n) 表示通用对数,底数差仅常数因子。 |
把 o 与 O 混为一谈 |
认为 o(g) 与 O(g) 相同。 |
o(g) 为 严格 上界(极限为 0),O(g) 只要求有常数上限。 |
| 使用极限时忘记整数化 | 直接把极限写成 ∞ 或 0 而不说明 n → ∞ 的离散性。 |
说明使用 连续延拓(把函数放到实数)或采用 Stirling、L’Hôpital 等严谨手段。 |
| 忘记对函数做非负化 | 对负数函数直接套用 Θ/Ω 定义会出错。 | 在算法分析里,运行次数、空间 均为非负;若出现负数,先取绝对值或重新建模。 |
6️⃣ 实践:自检练习¶
下面列出 10 组 常见比较题,建议自行手算极限或用 Python/Mathematica 验证。
| # | 比较 | 你的结论 | 解释/极限 |
|---|---|---|---|
| 1 | (n \log n) vs. (n^{1.1}) | (n\log n = o(n^{1.1})) | (\frac{n\log n}{n^{1.1}} = \frac{\log n}{n^{0.1}} \to 0) |
| 2 | (2^{n}) vs. (n^{\log n}) | (2^{n} = \omega(n^{\log n})) | (\log n \cdot \log n = (\log n)^2) vs. (n) in exponent → exponential dominates |
| 3 | (\sqrt{n}) vs. (\log n) | (\log n = o(\sqrt{n})) | (\frac{\log n}{\sqrt{n}} \to 0) |
| 4 | (n!) vs. (2^{n}) | (2^{n}=o(n!)) | Stirling: (n! \approx (n/e)^{n}) |
| 5 | ((\log n)^{2}) vs. (n^{0.01}) | ((\log n)^{2}=o(n^{0.01})) | (\frac{(\log n)^{2}}{n^{0.01}} \to 0) |
| 6 | (n^{\log n}) vs. (2^{\log^{2} n}) | 两者同阶 (\Theta(2^{\log^{2} n})) | (\log n = \log_{2} n) → (n^{\log n}=2^{(\log n)^{2}}) |
| 7 | (n^{\log_{2} 3}) vs. (3^{\log_{2} n}) | 同阶 (\Theta(n^{\log_{2} 3})) | 通过指数换底公式相同 |
| 8 | (n^{k}) vs. (c^{n})((c>1)) | (n^{k}=o(c^{n})) | Exponential dominates any polynomial |
| 9 | (n^{\log n}) vs. (2^{n}) | (n^{\log n}=o(2^{n})) | (\log n\cdot \log n = (\log n)^{2}) vs. linear (n) in exponent |
| 10 | (\log (n!)) vs. (n\log n) | (\log (n!) = \Theta(n\log n)) | Stirling: (\log(n!) = n\log n - n + O(\log n)) |
自检:如果你对任意一行没有把握,请回到极限公式或使用 Python 的
sympy.limit功能验证。
7️⃣ 代码实验:用 Python 自动判断 O/Θ¶
下面提供一个简易脚本,使用 SymPy(符号计算)判断两函数的关系。仅作为教学示例,实际使用时请结合手动推理。
# growth_compare.py
import sympy as sp
def compare(f, g, var=sp.Symbol('n', positive=True)):
"""Return relationship between f(n) and g(n):
'Theta', 'O', 'Omega', 'o', 'omega', or 'incomparable'."""
ratio = sp.simplify(f/g)
lim = sp.limit(ratio, var, sp.oo) # limit as n -> ∞
if lim.is_number:
if lim == 0:
return 'o' # f=o(g)
elif lim.is_infinite:
return 'omega' # f=ω(g)
else: # finite non‑zero constant
return 'Theta' # f=Θ(g)
else:
# fallback: check limsup/liminf numerically for some large values
vals = [10**k for k in range(3, 8)]
nums = [float(ratio.subs(var, v)) for v in vals]
if max(nums) < 10 and min(nums) > 0.1:
return 'Theta (numeric)'
elif max(nums) < 2:
return 'O (numeric)'
elif min(nums) > 0.5:
return 'Omega (numeric)'
else:
return 'inconclusive'
# --------------------------------------------------
if __name__ == '__main__':
n = sp.Symbol('n', positive=True, integer=True)
examples = [
(n*sp.log(n), n**1.1),
(2**n, n*sp.log(n)),
(sp.sqrt(n), sp.log(n)),
(sp.factorial(n), 2**n),
((sp.log(n))**2, n**0.01),
(n**sp.log(n,2), 2**(sp.log(n,2)**2)),
(n**sp.log(3,2), 3**sp.log(n,2)),
]
for f, g in examples:
print(f"f = {sp.simplify(f)}")
print(f"g = {sp.simplify(g)}")
print("Relation :", compare(f, g))
print("-" * 40)
运行后会输出每对函数的关系(如 Theta, o, omega),帮助 快速验证 直觉。
8️⃣ 进一步阅读与练手资源¶
| 类型 | 资源 | 链接 | 适合人群 |
|---|---|---|---|
| 教材 | 《算法导论》第 4 版(Cormen、Leiserson、Rivest、Stein) | https://mitpress.mit.edu/books/introduction-algorithms | 所有层次 |
| 讲义 | MIT 6.006 (Intro to Algorithms) 课程笔记 | https://ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2020 | 初学者 |
| 视频 | Stanford CS161 (Design & Analysis) 2023 | https://www.youtube.com/playlist?list=PL4B9E7E6E6EE8C71A | 进阶 |
| 交互式 | VisuAlgo – 视图所有常见增长函数的比较图 | https://visualgo.net/en | 直观理解 |
| 练习 | LeetCode “Algorithm” 标签(排序、搜索、图等) | https://leetcode.com/problemset/all/?topicSlugs=algorithm | 刷题 |
| 理论 | 《算法设计》(Kleinberg & Tardos)第 7 章 | https://www.cs.cornell.edu/home/kleinber/algorithms/ | 深入证明 |
| 数学 | 《Concrete Mathematics》章节 3(渐近分析) | https://doi.org/10.1017/CBO9781139644032 | 确切极限技巧 |
| 工具 | Anki 记忆卡片(常用函数、记号) | https://apps.ankiweb.net/ | 长期记忆 |
| 项目 | 实现一个 “增长函数可视化” WebApp(输入两函数,显示极限、绘制曲线) | 自行开发 | 巩固概念 + 编程实践 |
9️⃣ 小结¶
- 增长函数 抽象掉常数和低阶项,只保留“最高阶”行为。
- 大 O / Ω / Θ 给出上、下、紧确界;小 o / ω 表示严格的上下界。
- 使用 极限、摊销、递归树、主定理 等工具可以 系统地比较 任意两个函数。
- 常见函数族 按速率从慢到快排列,帮助快速判断算法的优劣。
- 误区(常数因子、对数底、o 与 O 的混淆)要时刻警惕,理论与实际的差距要通过实验验证。
- 实践:写代码实现、手算极限、用可视化工具或 Python 脚本自动对比,逐步形成对“函数增长速率直觉”的肌肉记忆。
掌握了这些概念后,你就能在阅读新算法时立刻判断它的 时间/空间规模,并在面试、研究或系统设计时做出 最合适的算法选择。祝学习愉快,成长为 增长函数的驾驭者! 🚀