English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

访客
1 / ?
返回课程列表

增长率,而不是绝对成本

Big O:测量成本如何增长

Big O 符号测量算法成本的增长率——而不是绝对成本。

Big O 要回答的问题:当输入大小 N 翻倍时,工作量是否翻倍?增加四倍?保持不变?

'O' 代表'数量级'。括号内的表达式描述了增长曲线的形状。

Big O 增长表:N=10、100 和 1,000 时 O(1)、O(N) 和 O(N²) 的操作次数

主要复杂度类别:

- O(1) — 常数: 成本不增长。例子:按索引在数组中查找值。数组有 10 项或 1000 万项,一次查找总是一次查找。

- O(N) — 线性: 成本随输入增长。例子:读取列表中的每一项一次。列表翻倍,读取次数翻倍。

- O(N²) — 平方: 成本作为输入的平方增长。例子:将每一项与其他每一项比较。N 翻倍,工作量增加四倍。

增长对比表:

NO(1)O(N)O(N²)
10110100
100110010,000
1,00011,0001,000,000

在 N=10 时差异看起来很小。在 N=1,000 时差距变得巨大。

比较 O(N) 和 O(N²)

使用上面的增长对比表。

在 N=1,000 时:O(N) 需要 1,000 次操作。O(N²) 需要 1,000,000 次操作。

在 N=1,000 时,O(N²) 比 O(N) 需要多多少倍的操作?展示你的计算过程。

代码结构如何揭示复杂性

如何从代码中识别 Big O

Big O 隐藏在你代码的形状中。四种模式涵盖了大多数情况:

单个循环遍历 N 项:O(N)

# O(N):一次通过列表
for item in list_of_n_items:
    process(item)

嵌套循环,每个都遍历 N 项:O(N²)

# O(N²):每一项与其他每一项配对
for item_a in list_of_n_items:
    for item_b in list_of_n_items:
        compare(item_a, item_b)

常数时间查找:O(1)

数组索引访问、哈希表查找——无论大小如何,一步。

分治(每一步将问题切成一半):O(log N)

二分查找:每次检查将剩余项减半。

---

隐藏的 O(N²):循环内的列表

# 这看起来像 O(N) 但实际上是 O(N²)
visited = []
for item in list_of_n_items:
    if item not in visited:   # 扫描所有 visited:O(N)
        visited.append(item)  # 整个循环:O(N²)

if item not in visited 这一行在每次迭代中扫描 visited 的每个元素。列表扫描成本为 O(N)。一个运行 N 次的循环,每次做 O(N) 的工作:O(N) × O(N) = O(N²)

这种模式出现在 1,000 多个开源项目中。修复只需一个字符:将 visited = [] 替换为 visited = set()。集合成员测试成本为 O(1),而不是 O(N)。一个改变。相同的结果。在 N=1,000 时快 1,000 倍。

分类和修复这段代码

阅读这段代码:

result = []
for name in student_names:
    if name not in result:
        result.append(name)
这段代码的 Big O 复杂度是多少?解释为什么 `if name not in result` 这一行使其成本高昂。然后重写代码使其为 O(N)。

理论与实践的碰撞

Big O 的实战应用

Big O 不仅是理论。它区分了在完全相同的任务上花费几秒钟的程序与花费 17 分钟的程序。

真实例子:构建系统中的依赖循环检测。

当你安装软件时,你的计算机解析一个依赖图:包 A 需要 B,B 需要 C,等等。构建系统检查这个图中是否有循环。

O(N²) 循环检测算法在 N=100 个包时工作良好。在 N=50,000 个包时(一个真实项目):它需要17 分钟。O(N) 修复:10 秒

这个确切的缺陷存在于 GHC(Haskell 编译器)、pip(Python 包管理器)、Maven(Java 构建系统)、Cargo(Rust 包管理器)& 1,000 多个其他项目中。

不是因为他们的作者不小心。visited = [] 是在 N 很小时写的。代码已经固化。N 增长了。没人注意到,直到构建需要半小时。

Big O 是允许你命名发生了什么的词汇——& 修复它。

---

想深入学习? 我们的 unhamming 课程包括关于 Big O 的完整章节、MOAD-0001(在 1,000 多个开源项目中发现的真实 O(N²) 缺陷),& 为什么给一个模式命名让你能够随处发现它。参见 缺失的章节:算法复杂度

预测构建时间

一个构建系统使用 O(N²) 循环检测。

测量的构建时间:

- N=100 个包:0.01 秒

- N=1,000 个包:1 秒

对于 O(N²):时间以 (N_new / N_old)² 的比例增长。

对于 O(N):时间以 (N_new / N_old) 的比例增长。

使用这些公式和测量数据:(A) 在 N=10,000 时,O(N²) 版本需要多长时间?(B) 在进行 O(N) 修复后,使用 N=1,000 作为你的基线,O(N) 版本在 N=10,000 时需要多长时间?展示两者的计算过程。