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

nu

访客
1 / ?
返回课程列表

增长率,而不是绝对成本

大 O:度量工作成本增长率

大 O 符号度量算法成本增长率,而不是绝对成本。

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

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

大 O 增长表:N=10、100 和 1,000 的操作数,对于 O(1)、O(N) 和 O(N²)

关键复杂性类别:

- O(1) — 常数:成本不增长。示例:在数组中通过索引查找值。无论数组有 10 项还是 10 万项,一次查找仍然是一次查找。

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

- O(N²) — 平方:成本随输入的平方增长。示例:将每个项目与其他每个项目进行比较。将 N 翻倍,工作量增加四倍。

增长比较表:

| N | O(1) | O(N) | O(N²) |

|---|------|------|-------|

| 10 | 1 | 10 | 100 |

| 100 | 1 | 100 | 10,000 |

| 1,000 | 1 | 1,000 | 1,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包管理器)以及1000多个其他项目中。

这不是因为它们的作者粗心大意。visited = []在N较小时被写入。代码逐渐变硬。N增长。没有人注意到,直到构建时间变为半小时。

Big O是让你能命名发生了什么的词汇——并修复它。

——

你想更深入了解吗? 我们的unhamming课程包括一整章关于Big O、MOAD-0001(一个O(N²)缺陷,在1000多个开源项目中发现)以及为什么命名一个模式让你能在任何地方找到它。见[缺失章节:算法复杂性](/en/un/unhamming_algorithmic_complexity)。

预测构建时间

构建系统使用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时需要多长时间?在两个方面都展示你的工作。