同一件事有两种写法:一种不需要准备,拿到数据就开始算;另一种先整理出一份索引,之后每次查询都很快。只查一次时前者更省;查十万次时后者可能反超。
"哪个更快"这个问题缺少主语。完整的问题至少包含四项:输入规模、查询次数、设备、允许误差。四项定了,算法之间才有共同的比较标准。
预计用时:30 分钟。本篇说明这门课的方法、契约、路线与交付物,并从《揭开黑盒》第二章的 章末接口 接续。
一份 n 项数据,任务是回答 q 次"某个数在不在里面",每次返回是否。
以 n = 100000、每次二分 17 次比较计:
| 查询次数 q | 方案 A 比较次数 | 方案 B 比较次数 | 更省的一方 |
|---|---|---|---|
| 1 | 100000 | 约 1.7×10⁶(含排序) | A |
| 10 | 1.0×10⁶ | 约 1.7×10⁶ | A |
| 100 | 1.0×10⁷ | 约 1.7×10⁶ | B |
| 100000 | 1.0×10¹⁰ | 约 1.7×10⁶ | B |
分歧不在"谁更快",在转折点的位置。这个位置依赖 n、q、排序与比较的常数因子,是可以测的,不需要争。
两种解释可以同时成立:
一次实验能区分它们:固定 n 与数据,只改 q,记录两条曲线的交点。
沿用 算法课堂接续任务单。八项缺一不算交付:
| 字段 | 要求 |
|---|---|
| 输入与输出 | 规模、取值范围、输出含义 |
| 正确性 | 独立参考或可检查性质;误差上限;非法输入的处理 |
| 基线实现 | 可运行、有版本号的起点 |
| 候选变化 | 只改公式、顺序、表示、预处理、并行、近似中的一项 |
| 成本账本 | 计算、搬运、准备、存储、同步、维护分开记 |
| 实验设计 | 固定什么、改变什么、什么观察会反驳预测 |
| 证据 | 原始样本、运行环境、计时边界、失败路径 |
| 未解决的问题 | 哪一项原因尚未排除,下一步打开哪一层 |
两条要求需要单独说明。
正确性一栏不能写"跑过几个样例看起来对"。 有效内容只有两类:一个独立参考(暴力解、整数精确解、另一份实现的输出),或一条可检查性质(输入的排列不变性、已知的恒等式、边界值)。没有这两类之一,后面所有计时都不成立——一个算错的程序可以跑得很快。
成本账本不是只记运算次数。 六项要分开:计算(乘加次数)、搬运(内存与网络访问)、准备(预处理)、存储(额外数据结构)、同步(并行时的协调)、维护(代码与状态变更的复杂度)。常见错误是只记第一项,于是漏掉"运算少但准备贵"的反例。
| 规矩 | 违反后的症状 |
|---|---|
| 先有独立参考,再比较快慢 | 性能曲线正确,答案错误 |
| 一次只改一个条件 | 无法把差异归因到算法 |
| 写下尚未排除的原因 | 结论看似确定,实为未被追问 |
第三条对应《揭开黑盒》贯穿全书的表述:"我现在还不知道它为什么这样,但我知道下一步可以怎样查。" 这门课不要求每单元都有定论,要求每个结论都标注它的适用范围。
| 出发的问题 | 新增的方法 |
|---|---|
| 改循环与分块仍不够 | 分治、渐近复杂度、交叉规模 |
| 重复查询越来越多 | 排序、查找、哈希、树、摊还分析 |
| 数据大量为空或有结构 | 稀疏表示、图表示、压缩、局部性 |
| 长计算链挡住并行 | 归约、扫描、依赖图、通信代价 |
| 迭代如何更快接近答案 | 求根、优化、数值稳定性、收敛条件 |
| 近似能换来什么 | 随机化、采样、误差界、概率保证 |
这是问题清单,不是目录保证。每单元从一个具体问题进入,交付一套方法、一份证明、一次可复核的实验。
第一单元接续 assets/math-lab/ 的矩阵乘法。改循环顺序与分块之后,乘法次数没有变,因此存在天花板。下一步不是继续调块大小,而是减少乘法次数本身——这会引入分治,以及实践里绕不开的一个量:交叉规模(crossover point),即两种做法耗费相等的规模阈值。
开篇提出的"转折发生在第几次"是它的一个实例。第一单元要回答的是:这个阈值由什么决定,怎样测出来,什么条件下会移动。
《揭开黑盒》计划转向"给现实中的物品注入灵魂"。一个会感知、保持状态、作出判断并反馈的物品,必须回答三问:该算多准、最多等多久、算不出来时返回什么。这三问的依据来自本课程——精度、延迟与成本如何随算法改变。
本课程的产出不是一份算法排名,而是一套判断:面对具体任务,说清为什么选这个算法,以及别人给出什么条件时会改选。