⚠ 当前无法加载公式渲染库(KaTeX 走 CDN),公式可能显示为原始代码——联网后刷新即可。
算法设计与分析 · 课程笔记 · 模块 1(CH01–CH02)

模块 1 · 算法概述 与 递归分治

合并课件 CH01-概述v5(85 页)+ CH02-分治v4(127 页) 教材:王晓东《计算机算法设计与分析》 整理日期:2026-09-25
本科遗忘急救站 · 分治与主定理必备的 3 个离散数学基础

0 · 对数换底、等比级数与递归树:3 分钟找回算法复杂度直觉

  • 对数公式(折半次数的来源):若每次规模除以 \(b\),经过 \(k\) 层后规模变为 \(n/b^k = 1 \iff k = \log_b n\)。换底公式:\(\log_a n = \dfrac{\log_b n}{\log_b a}\),相差仅常数倍,故大 \(O\) 记号中底数可省写为 \(O(\log n)\)。记住重要恒等式:\(a^{\log_b n} = n^{\log_b a}\)(这是递归树第 \(k=\log_b n\) 层叶子总数等于 \(n^{\log_b a}\) 的代数原因!)。
  • 主定理 \(T(n) = aT(n/b) + f(n)\) 的本质是“整棵递归树等比求和谁占大头”:设深度 \(L=\log_b n\),第 \(j\) 层有 \(a^j\) 个子问题,每个开销 \(f(n/b^j)\)。比较叶子层总开销 \(n^{\log_b a}\) 与根节点分裂合并开销 \(f(n)\):
    1. 若 \(f(n) = O(n^{\log_b a - \varepsilon})\)(上轻下重,公比 \(>1\)):叶子层占主导,\(T(n) = \Theta(n^{\log_b a})\);
    2. 若 \(f(n) = \Theta(n^{\log_b a}\log^k n)\)(上下均势,每层开销同阶):共 \(\log n\) 层累加,\(T(n) = \Theta(n^{\log_b a}\log^{k+1} n)\);
    3. 若 \(f(n) = \Omega(n^{\log_b a + \varepsilon})\) 且满足正则条件 \(af(n/b)\le cf(n)\ (c<1)\)(上重下轻,公比 \(<1\)):根层占主导,\(T(n) = \Theta(f(n))\)。

自学起点 · 从遗忘基础到考试答题

先补的数学

对数解决“反复折半”的层数问题:n 连续减半,约 log₂n 次到 1;等差求和 1+⋯+n=n(n+1)/2;递归树 T(n)=2T(n/2)+n 的每层工作量为 n,共约 log₂n 层,因此 Θ(n log n)。

手算例题 · 二分搜索

有序数组 [1,3,5,8,10] 查找 8。先看索引 2,值 5,小于 8,目标只能在右半边;再看索引 3,值 8,找到。循环始终保持“若目标存在,它在当前搜索区间”的不变式。迭代时间 O(log n),额外空间 O(1)。

💡 破题关键与第一步提示(遮答案自测用 · 考前自测:快速排序为何不是任何输入都 O(n log n)?)

破题切入点:划分均匀时递归深度约 log n;总是选到极端划分元时 T(n)=T(n−1)+Θ(n)=Θ(n²)。(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(考前自测:快速排序为何不是任何输入都 O(n log n)?)

划分均匀时递归深度约 log n;总是选到极端划分元时 T(n)=T(n−1)+Θ(n)=Θ(n²)。区分最坏、平均与最好复杂度。

考点精练 · 教学型推导与标准答题

考试演算:归并排序递推式为什么是 \(\Theta(n\log n)\)?

题型:给出一个规模 \(n\) 的问题,每次分成两个规模 \(n/2\) 的子问题,划分与合并耗时 \(\Theta(n)\)。写递推式、求复杂度并解释。

  1. 列式:\(T(n)=2T(n/2)+cn\),且 \(T(1)=\Theta(1)\)。
  2. 逐层展开:第 \(i\) 层有 \(2^i\) 个规模 \(n/2^i\) 的子问题,合并总代价 \(2^i c(n/2^i)=cn\)。
  3. 层数:经过 \(\log_2n\) 次折半规模达到 1,共 \(\Theta(\log n)\) 个非叶层;各层总成本为 \(n\log_2n\),叶层总成本为 \(\Theta(n)\)。
  4. 结论:\(T(n)=\Theta(n\log n)\)。由主定理 \(a=b=2\),\(n^{\log_ba}=n\),属于第二情形。
💡 破题关键与第一步提示(遮答案自测用 · 自己做:\(T(n)=3T(n/2)+n\)(假设 \(n=2^k\)))

破题切入点:\(n^{\log_2 3}\) 比 \(n\) 增长更快,满足主定理第一情形,故 \(T(n)=\Theta(n^{\log_2 3})\)。(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(自己做:\(T(n)=3T(n/2)+n\)(假设 \(n=2^k\)))

\(n^{\log_2 3}\) 比 \(n\) 增长更快,满足主定理第一情形,故 \(T(n)=\Theta(n^{\log_2 3})\)。考试时写明 \(a=3,b=2,f(n)=n\)。

Python 衔接:二分查找用 left, right 维持闭区间不变式;若返回第一次出现的位置,条件分支要单独处理重复值,不能把“找任意一个”和“找最左侧”写成同一个解。

修订版 · 重点核对

先看:本模块易错点与补充

理论校准
  • 大 O 是某类运行时间的渐近上界,不自动代表“最坏时间”。比较两个增长率应用 f(n)=o(g(n)),不要写 O 集合的“小于”。
  • 比较排序的 Ω(n log n) 下界与非比较排序不可混淆;固定主元快排在特殊输入下会退化。
  • 二分与归并的 Python 版本和可执行测试见 Python 求职衔接。

0 · 课程与考核CH01 S1–5; Review S93

项目内容(两届课件略有出入,以当年通知为准)
学时 / 教师32 学时;刘瑶老师(沙河主楼中 427)
成绩构成CH01 版:课堂参与 20% + 课程报告/讲座 30% + 期末 50%;Review 版:平时 60% + 期末开卷 40%。缺席 4 次以上没有平时成绩
期末题型(Review 版)选择 20% + 判断 20% + 填空 30%(含算法填空)+ 问答 10%(复杂度分析)+ 算法设计 20%
期末题型(2021 大纲版)判断 10 题(20 分)+ 简单计算 20–30 分(渐进式 / 贪心 / 分治递归式 / DP / 最大流最小割 / NPC 证明 2–3 题 / 近似)
复习主线复习资料/2021年复习大纲.pptx(16 页 = 全部考点)+ Review-v2.pptx(知识点浓缩 + 例题)
大白话 · 整门课一张图 六大设计方法就是六种“对付问题的姿势”:分治(切成独立的小块)、动态规划(切块但有重叠,记住答案)、贪心(每步拿眼前最好的)、回溯(深度优先试错+剪枝)、分支限界(广度优先+界函数)、近似算法(NPC 问题放弃精确、量化“差多少”)。判定一个新问题该用哪个姿势,是这门课的核心能力。
本模块考试重点(按 2021 大纲 + Review 题型)
  1. 渐近符号与阶排序——判断/选择高频:O/Ω/Θ 定义辨析、把 n, log n, n log n, n², 2ⁿ, n! 从小到大排序;
  2. 机器提速经典题——"机器快 64 倍/100 倍能多处理多大规模"(本模块例 1、例 2 两种皮肤都要会,历年反复出现);
  3. 递归式求解——Master 定理三情形 + 特征方程法(填空/计算,"典型题 · 解递推关系"是模板);
  4. 分治适用四条件与递归三要素——判断/简答;
  5. Hanoi / 全排列 / 整数划分——算法填空递归代码或写递归式。
答题规范(大纲原话,每条都值分) ①算法题禁止写程序——用自然语言描述思想 + 关键步骤;②计算题写过程(递归式要写求解步骤,不能只给答案);③比较渐近阶要给理由(极限法或比值法)。
数学手生?增长率比较(对数<幂<指数)、求和公式、矩阵运算的"三分钟复习"在上级目录 《数学基础工具箱》第 3、4、5 节;考前看第 8 节"急救包"。

1 · 算法是什么CH01 S26–33

定 义 给定计算问题,算法是一系列良定义的计算步骤,逐一执行即可得到预期的输出(输入 → 步骤 → 输出)。
算法 vs 程序:程序是算法用某种程序设计语言的具体实现;算法的有穷性意味着不是所有程序都是算法(如操作系统死循环)——这是判断题高频点。
课件金句 “如果你在森林里迷路了,走一步看一步”——是建议而非算法;“一直往下走到溪流,顺流而下到城镇”——有穷、确定、可执行,这才是算法。

算法分类:数值型算法(基本运算为算术运算)与非数值型算法。算法与数据结构的关系:算法 + 数据结构 = 程序;数据的逻辑结构与存储结构共同决定实现方式。

2 · 复杂性计量与渐近符号CH01 S34–58; Review S6–14

复杂性 = 时间复杂性 + 空间复杂性。同一算法运行时间受硬件、语言、输入数据影响,所以只比较增长趋势:

三种情况 最坏 $T_{\max}(n)$、最好 $T_{\min}(n)$、平均 $T_{\mathrm{avg}}(n)$。通常分析最坏情况(提供上界保证)。
渐近符号定义(判断题高发)
记号定义含义
\(f(N)=O(g(N))\)∃ 正常数 \(c, n_0\),∀\(n\ge n_0\): \(|f(N)|\le c|g(N)|\)g 是 f 的上界(阶不高于 g)
\(f(N)=\Omega(g(N))\)∃ \(c, n_0\): \(|f(N)|\ge c|g(N)|\)g 是 f 的下界
\(f(N)=\Theta(g(N))\)∃ \(c_1,c_2,n_0\): \(c_1 g\le|f|\le c_2 g\)同阶(= O 且 = Ω)
推论:问题的计算时间下界为 \(\Omega(f(n))\) 时,复杂度 \(O(f(n))\) 的算法是最优算法。例:排序下界 \(\Omega(n\log n)\),归并排序 \(O(n\log n)\) 即最优。
大白话 O 表示渐近上界,须先说明研究的是最坏、平均还是期望运行时间;它不天然等于最坏情况。(增长率的天花板),Ω 回答“至少要多久”,Θ 回答“就是这个量级”。比较算法只看最高阶项:\(T(n)=3n^2+4n\log n+7\) 的阶就是 \(\Theta(n^2)\)——系数和低阶项在 n 充分大后无关紧要,这就是“常数倍差别可忽略”的原因。

复杂性层次:\(1\prec\log n\prec n\prec n\log n\prec n^2\prec n^3\prec\dots\prec2^n\prec n!\)(按渐近增长快慢排序,\(f\prec g\) 表示 \(f=o(g)\))。多项式时间算法(n 在底数位置)被视为“可解”;指数/阶乘级(n 在指数上)被认为不可承受。

3 · 分析法则与常见函数阶CH01 S61–84

非递归算法分析四法则 ① 循环 = 循环体内时间 × 循环次数;② 嵌套循环 = 内层时间 × 所有层次次数;③ 顺序语句 = 相加;④ if-else = 取两分支的较大者。
常用关系式
  • 多项式:\(a_0+a_1n+\cdots+a_dn^d=\Theta(n^d)\)(\(a_d>0\));
  • 对数换底:\(\log_a n=O(\log_b n)\)(底数不重要,都是 \(\Theta(\log n)\));对任意 \(a>0\):\(\log n=o(n^a)\)(对数永远慢于任意正幂);
  • 指数碾压:\(n^b=o(a^n)\)(\(a>1\));\(n^{100}=o(1.01^n)\)(最终);
  • 取整:\(\lfloor n/2\rfloor+\lceil n/2\rceil=n\),\(\lceil\lceil n/a\rceil/b\rceil=\lceil n/(ab)\rceil\)\;\text{(a,b 为正整数)};
  • Stirling:\(n!\approx\sqrt{2\pi n}\,(n/e)^n\)。
典型题 · 渐近阶排序
把 \(4n^2,\ \log n,\ 3^n,\ 20n,\ 2,\ n^{2/3},\ n!\) 按增长率从低到高排序。
💡 破题关键与第一步提示(遮答案自测用 · 解答)

破题切入点:\(2 \prec \log n \prec n^{2/3} \prec 20n \prec 4n^2 \prec 3^n \prec n! \cdots\)(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(解答)
\(2 \prec \log n \prec n^{2/3} \prec 20n \prec 4n^2 \prec 3^n \prec n!\) (口诀:常数 < 对数 < 分数幂 < 线性 < 多项式 < 指数 < 阶乘;注意 \(n^{2/3}<n\)。)
典型题 · 求渐近表达式
\(3n^2+10n=\Theta(n^2)\);\(n^2/10+2^n=\Theta(2^n)\);\(21+1/n=\Theta(1)\);\(\cos n\le1\Rightarrow\cos n=O(1)\)。

4 · 经典计算题:机器提速问题 ★(历年必考原型)CH01 S73–74; Review S97–100

例 1 · 64 倍速机器
算法时间 \(T(n)=3\times 2^n\),某机器上耗时 t 秒解规模 n。新机器快 64 倍,t 秒能解多大规模?
💡 破题关键与第一步提示(遮答案自测用 · 解答)

破题切入点:\(3\cdot 2^{n_1}=64\cdot 3\cdot 2^n\Rightarrow 2^{n_1}=2^{n+6}\Rightarrow n_1=n+6\)——指数时间算法提速只多解 6 个规模!(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(解答)
\(3\cdot 2^{n_1}=64\cdot 3\cdot 2^n\Rightarrow 2^{n_1}=2^{n+6}\Rightarrow n_1=n+6\)——指数时间算法提速只多解 6 个规模! 若改进为 \(T(n)=n^2\):\(n_1^2=64n^2\Rightarrow n_1=8n\)——多项式时间算法提速获得倍数级规模。$T(n)=$常数时任意规模。∎
例 2 · 100 倍速机器(XYZ vs ABC 公司)
复杂度分别为 \(n,\ n^2,\ n^3,\ n!\) 的算法,原机器 1 小时解规模 n,新机器快 100 倍,各能解多大?
💡 破题关键与第一步提示(遮答案自测用 · 解答(课件标准答案))

破题切入点:① \(n'=100n\);② \(n'^2=100n^2\Rightarrow n'=10n\);(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(解答(课件标准答案))
① \(n'=100n\);② \(n'^2=100n^2\Rightarrow n'=10n\);③ \(n'^3=100n^3\Rightarrow n'=\sqrt[3]{100}\,n\approx 4.64n\);④ \(n'!\approx 100\cdot n!\),由 Stirling 公式 \(n'<n+\log 100=n+6.64\)。 结论(判断题爱考):指数/阶乘级算法再快的机器也无济于事;这就是“指数增长的恐怖”。
例 3 · 算法改进
\(T(n)=n^3\) 毫秒改进为 \(T(n)=n^2\)。原来运行 1 小时的实例现在要多久?
💡 破题关键与第一步提示(遮答案自测用 · 解答)

破题切入点:\(n^3=3.6\times10^6\Rightarrow n\approx153\);(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(解答)
\(n^3=3.6\times10^6\Rightarrow n\approx153\);新时间 \(=n^2\approx153^2\approx2.4\) 万毫秒 ≈ 23.3 秒。

5 · 递归:概念与四大例题CH02 S3–38

递归三要素 ① 边界条件(终止,必须有!);② 递归方程(用更小规模的自身定义);③ 逐层调用通过递归工作栈(参数、局部变量、返回地址,后进先出)管理——所以递归的空间开销 = 栈深。

三种适用场景:问题定义是递归的(阶乘、Fibonacci);数据结构是递归的(二叉树遍历);求解过程是递归的(Hanoi、全排列)。

例 1 · Fibonacci:递归 vs 递推(复杂度对比经典)
递归式 \(F_n=F_{n-1}+F_{n-2}\)。递归版调用次数 \(\mathrm{NumCall}(k)=2F_{k+1}-1\),复杂度 O(2ⁿ)(大量重复子问题——DP 的伏笔);递推版保存前两项,O(n)。
例 2 · Hanoi 塔 ★
n 盘从 A 借助 B 移到 C:① n−1 盘 A→B;② 最大盘 A→C;③ n−1 盘 B→C。\(T(n)=2T(n-1)+1\Rightarrow\) 移动次数 \(2^n-1\)(n=64 时约 \(1.8\times10^{19}\) 次,5845 亿年)。
例 3 · 全排列 Perm
R 的全排列 = 每个元素 \(r_i\) 作前缀 + \(R-\{r_i\}\) 的全排列。实现:依次把每个元素换到位置 k,递归排列 k+1..m,再换回(swap-and-backtrack——回溯法排列树的前身)。
例 4 · 整数划分 q(n,m)
n 的划分数(最大加数不超过 m): \[q(n,m)=\begin{cases}1, & n=1\ \text{或}\ m=1\\ q(n,n), & n<m\\ q(n,n-1)+1, & n=m\\ q(n,m-1)+q(n-m,m), & n>m>1\end{cases}\] 答案 \(p(n)=q(n,n)\)。例:6 有 11 种划分。这是递归式设计的经典练习。

6 · 分治法:思想与适用条件CH02 S39–49

三步骤 Divide 分解(大问题 → k 个规模更小的相同子问题)→ Conquer 求解(递归,规模足够小直接解)→ Combine 合并(子解拼成原解)。经验:均衡子问题(子问题规模大致相等)几乎总是更好。
适用四条件 ★(判断题高发) ① 规模缩小到一定程度容易解决;② 可分解为若干规模较小的相同问题(最优子结构);③ 子问题的解可以合并为原问题的解(能否用分治完全取决于此条);④ 子问题相互独立(不含公共子问题)。
关键推论:满足①②而不满足③ → 考虑贪心或动态规划;满足①②③但④不成立(子问题重叠)→ 动态规划。

7 · Master 定理 ★★(大题必考)CH02 S51; Review S21

Master 定理(递归复杂度判定) 设 \(T(n)=a\,T(n/b)+f(n)\),\(a\ge1,\ b>1\),记 \(E=\log_b a\)(递归树叶重的指数):
  1. 若 \(f(n)=O\big(n^{E-\varepsilon}\big)\)(f 多项式地慢),则 \(T(n)=\Theta\big(n^{E}\big)\);
  2. 若 \(f(n)=\Theta\big(n^{E}\big)\)(同阶),则 \(T(n)=\Theta\big(n^{E}\log n\big)\);
  3. 若 \(f(n)=\Omega\big(n^{E+\varepsilon}\big)\)(f 多项式地快),且对某 \(c<1\) 充分大有 \(a f(n/b)\le c f(n)\),则 \(T(n)=\Theta\big(f(n)\big)\)。
速算对照表(本课所有递归式)
算法递归式结论
二分搜索\(T(n)=T(n/2)+O(1)\)\(\Theta(\log n)\)(a=1,b=2,E=0,情形1/2边界)
合并排序\(T(n)=2T(n/2)+O(n)\)\(\Theta(n\log n)\)(情形2)
Strassen\(T(n)=7T(n/2)+\Theta(n^2)\)\(\Theta(n^{\log_2 7})\approx\Theta(n^{2.81})\)(情形1)
普通矩阵乘\(T(n)=8T(n/2)+\Theta(n^2)\)\(\Theta(n^3)\)(情形1)
最接近点对\(T(n)=2T(n/2)+O(n)\)\(\Theta(n\log n)\)(情形2)
棋盘覆盖\(T(k)=4T(k-1)+O(1)\)\(\Theta(4^k)\)(n=2ᵏ 时即 Θ(n²) 个骨牌)
典型题 · 解递推关系
\(X(n)=X(n/3)+1,\ X(1)=1,\ n=3^k\)。(Review 问答题原题)
💡 破题关键与第一步提示(遮答案自测用 · 解答)

破题切入点:\(X(n)=X(n/3^k)+k=1+\log_3 n\)。(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(解答)
\(X(n)=X(n/3^k)+k=1+\log_3 n\)。∎(换元展开法——不满足 Master 标准形式时的万能手法。)

8 · 分治六大经典算法

8.1 二分搜索 O(log n)

已排序数组取中间元素比较,每次规模减半。最坏 while 执行 O(log n) 次、每次 O(1) → O(log n)。变体题(作业):x 不在数组中时返回小于 x 的最大位置 i 与大于 x 的最小位置 j。

8.2 大整数乘法 减乘法次数的思想

朴素 \(O(n^2)\)。分治 \(X=a\cdot2^{n/2}+b,\ Y=c\cdot2^{n/2}+d\):直接拆要 4 次子乘法;改进为 \[XY=ac\cdot2^n+\big[(a+b)(c+d)-ac-bd\big]2^{n/2}+bd\] 只需 3 次子乘法:\(T(n)=3T(n/2)+O(n)=\Theta(n^{\log_2 3})\approx O(n^{1.59})\)。思想:降低复杂度的关键是减少子乘法次数(判断题:不是减少加法)。

8.3 Strassen 矩阵乘法 O(n^2.81)

分块后同样从 8 次子块乘法降到 7 次(Strassen 的 7 个中间矩阵 M1–M7):\(T(n)=7T(n/2)+\Theta(n^2)=\Theta(n^{\log_2 7})\approx n^{2.81}\)。已证明 2×2 矩阵 7 次乘法是必要的;当前还有渐近更快的矩阵乘法算法,但具体理论指数会随研究进展变化;课堂重点是 Strassen 的 \(\log_2 7\)。

8.4 棋盘覆盖 分治构造

\(2^k\times2^k\) 棋盘恰有一特殊方格,用 L 型骨牌覆盖其余。分治:十字中心放置一块 L 骨牌(不含特殊方格的三个象限各占一格),把问题化为 4 个 \(2^{k-1}\) 子棋盘。骨牌数 \((4^k-1)/3\),时间 \(\Theta(4^k)\)。作业常考完整实现。

8.5 快速排序 ★ 划分元决定一切

要点 选划分元 → 一趟 Partition(小于放左、大于放右)→ 两侧递归。最好/平均 O(n log n)(划分对称);最坏 O(n²)(每次选到最值,序列已有序时蜕化为冒泡)。改进:① 三者取中(首、尾、中间元素的中间值);② 随机化划分元(RandomizedPartition,期望 O(n log n))。

8.6 线性时间选择 ★ 找第 k 小

两级算法

RandomizedSelect(期望 O(n)):随机划分元,只递归进入包含第 k 小的一侧(对比快排两侧都递归)。

Select(最坏 O(n),中位数的中位数):① n 个元素按 5 个一组排序,取出各组中位数(共 ⌈n/5⌉ 个);② 递归求这些中位数的中位数 x;③ 用 x 划分。关键不等式:x 至少比 \(3\lfloor(n-5)/10\rfloor\) 个元素大(和小),子问题规模 ≤ \(3n/4\),而 \(n/5+3n/4=19n/20=0.95n=\varepsilon n\)——两递归自变量之和按常数比例收缩,故 \(T(n)=O(n)\)。分组大小 5 与阈值 75 是关键(判断/填空点)。

8.7 最接近点对 计算几何经典

二维 O(n log n)

一维:排序后中位数 m 分两半,递归得 d₁,d₂,d=min;合并时只需检查 (m−d, m] 与 (m, m+d] 两段——每段至多 1 个点。

二维:垂直线 x=m 分割;合并时只需检查宽 2d 的带状区。带内点按 y 排序,按 y 顺序对每个点检查其后至多 7 个候选点(经典条带打包论证给出常数上界;各半区内已知最小距离至少 d,不能仅用“分成六块”替代严谨证明)。合并 O(n): \[T(n)=2T(n/2)+O(n)=O(n\log n).\] 朴素两两比较是 O(n²)——分治收益巨大。∎

8.8 循环赛日程表 分治构造题

n=2ᵏ 个选手,每选手与其他 n−1 人各赛一次、每天至多一场,共 n−1 天。分治填表:左上角(前半选手前半程)→ 左下 = 左上 + \(2^{k-1}\) → 右上 = 左下 → 右下 = 左上。8 人日程表是问答真题(Review S107)。

📄 期末禁止写代码:分治法经典算法(二分查找 / 归并排序 / 线性时间选择) · 自然语言标准作答四步模板
  1. ① 状态/子问题定义与符号声明:设原输入规模为 \(n\),子问题区间为 \([low, high]\);归并排序将数组均分为左右两半,线性选择以主元 \(pivot\) 划分为小于、等于、大于三个子集。
  2. ② 核心递推方程 / 贪心策略 / 搜索剪枝准则:归并排序满足 \(T(n)=2T(n/2)+O(n)\);二分查找满足 \(T(n)=T(n/2)+O(1)\);五元中位数线性选择满足 \(T(n)\le T(\lceil n/5\rceil)+T(7n/10+6)+O(n)\)。
  3. ③ 算法自然语言分步流程(严禁贴 C/Python 语法):第一步(Divide):计算中点或选定主元划分数组;第二步(Conquer):递归求解子区间(二分查找与线性选择仅递归进入含目标的单一分支);第三步(Combine):双指针线性合并两个有序子数组(二分与选择无需合并步)。
  4. ④ 时间与空间复杂度严格推导:由 Master 定理,归并排序 \(a=2,b=2,f(n)=\Theta(n)=\Theta(n^{\log_b a})\) 属情形 2,时间 \(O(n\log n)\)、空间 \(O(n)\);二分查找时间 \(O(\log n)\);线性选择最坏/平均时间 \(O(n)\)。
🐍 Python 求职机试标准实现(源自 practice_algorithms.html · 在线阅读器)
def lower_bound(nums, target):
    """已排序 nums 的第一个 >= target 的下标。O(log n) 时间。"""
    lo, hi = 0, len(nums)
    while lo < hi:
        mid = (lo + hi) // 2
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo

def merge_sort(nums):
    """稳定归并排序。O(n log n) 时间、O(n) 额外空间。"""
    if len(nums) < 2:
        return list(nums)
    mid = len(nums) // 2
    a, b = merge_sort(nums[:mid]), merge_sort(nums[mid:])
    i = j = 0
    out = []
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            out.append(a[i])
            i += 1
        else:
            out.append(b[j])
            j += 1
    return out + a[i:] + b[j:]

def quick_select(nums, k):
    """三路划分求 0-indexed 第 k 小元素。平均 O(n) 时间。"""
    if not 0 <= k < len(nums):
        raise ValueError('k 越界')
    arr = list(nums)
    while True:
        pivot = arr[len(arr) // 2]
        lt = [x for x in arr if x < pivot]
        eq = [x for x in arr if x == pivot]
        gt = [x for x in arr if x > pivot]
        if k < len(lt):
            arr = lt
        elif k < len(lt) + len(eq):
            return pivot
        else:
            k -= len(lt) + len(eq)
            arr = gt
分割中线 x = m 左带 δ 右带 δ 高 δ (2×δ/2) 当前点 p(x,y) ① ② ③ ④ ⑤ ⑥ 📌 为什么跨区合并只需 O(n) 而非 O(n²)? 1. 递归求出左半最小距 δ_L、右半最小距 δ_R,令 δ=min(δ_L,δ_R)。 2. 滤出中线两侧距离 < δ 的带状区点,按 y 坐标升序排列。 3. 将右半 δ×δ 矩形均分为 4 个 (δ/2)×(δ/2) 小方格,每个小方格 对角线长为 (√2/2)δ ≈ 0.707δ < δ。 4. 鸽巢原理:同侧若某小格含 ≥2 点,其间距必 < δ(矛盾)! ★ 结论:对每个点 p,只需向后检查最多 6 个候选点(O(1)), 合并步耗时 O(n),总时间 T(n)=2T(n/2)+O(n)=O(n log n)。
图:分治法最接近点对——中线 2δ×δ 候选框划分为 (δ/2)×(δ/2) 小格,由鸽巢原理证得单点至多比较 6 次

补齐历史大纲重点:逆序对计数 Counting Inversions

来源:仓库 复习资料/2021年算法设计与分析复习大纲.pptx 第 4 页明确列出;是 2021 年旧大纲要求,不代表已确认 2026 年考核范围。

题意:在数组中统计满足 \((i,j):i<j,\;a_i>a_j\) 的下标对总数。例如 [2,4,1,3,5] 中共有三对:(2,1)、(4,1)、(4,3),答案为 3。

  1. 分:拆成左半与右半;逆序对分为全左、全右、横跨左右三类,互不重叠且覆盖全部情况。
  2. 治:递归得到左右排序结果和各自内部的逆序对数。
  3. 合:归并时若 \(L_i\le R_j\),先取左元素,不产生新的跨区逆序对;若 \(L_i>R_j\),由于左半段已排序,从当前 i 到左半末尾的所有元素都大于 \(R_j\),一次性累加 \(|L|-i\)。
  4. 复杂度:合并 O(n),递推 \(T(n)=2T(n/2)+\Theta(n)=\Theta(n\log n)\),与暴力枚举的 O(n²) 对比。
💡 破题关键与第一步提示(遮答案自测用 · 展开 Python 参考实现(面试代码;考试按老师要求描述思想))

破题切入点:def inversion_count(nums): def solve(a): if len(a) <= 1: return a, 0 m = len(a) // 2 left, c(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(展开 Python 参考实现(面试代码;考试按老师要求描述思想))
def inversion_count(nums):
    def solve(a):
        if len(a) <= 1:
            return a, 0
        m = len(a) // 2
        left, cnt_l = solve(a[:m])
        right, cnt_r = solve(a[m:])
        i = j = 0
        merged = []
        count = cnt_l + cnt_r
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                merged.append(left[i])
                i += 1
            else:
                count += len(left) - i
                merged.append(right[j])
                j += 1
        merged.extend(left[i:])
        merged.extend(right[j:])
        return merged, count

    return solve(nums)[1]

assert inversion_count([2, 4, 1, 3, 5]) == 3
assert inversion_count([1, 1, 1]) == 0
assert inversion_count([3, 2, 1]) == 3

注意 <= 是刻意使用的:相等值不是逆序对,别因比较符号写错多计。

💡 破题关键与第一步提示(遮答案自测用 · 自测:数组 [3,1,2,1] 有多少个逆序对?)

破题切入点:答案 4:3>1、3>2、3>1、2>1。(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(自测:数组 [3,1,2,1] 有多少个逆序对?)

答案 4:3>1、3>2、3>1、2>1。两个相等的 1 不构成逆序对。

📄 期末禁止写代码:分治统计逆序对(Counting Inversions) · 自然语言标准作答四步模板
  1. ① 状态/子问题定义与符号声明:设数组 \(A[1..n]\),逆序对定义为满足 \(iA[j]\) 的二元组。将其拆分为左半内部逆序对、右半内部逆序对、以及跨越中点 \(mid\) 的逆序对三部分。
  2. ② 核心递推方程 / 贪心策略 / 搜索剪枝准则:归并左右两个已排序子数组 \(L\) 与 \(R\) 时,若当前指针处 \(L[i] \le R[j]\) 则无新增跨区逆序对;若 \(L[i] > R[j]\),则 \(L[i..|L|]\) 全部大于 \(R[j]\),一次性累加跨区逆序对数 \(|L|-i+1\)。
  3. ③ 算法自然语言分步流程(严禁贴 C/Python 语法):第一步:若区间长度 \(\le 1\) 直接返回 0;第二步:以中点 \(mid\) 递归统计左半得 \(c_1\)、右半得 \(c_2\) 并各自排好序;第三步:用双指针线性归并左右有序表并同步累加跨区逆序对 \(c_3\),返回 \(c_1+c_2+c_3\)。
  4. ④ 时间与空间复杂度严格推导:递归方程 \(T(n)=2T(n/2)+\Theta(n)\),由 Master 定理情形 2 得时间复杂度 \(\Theta(n\log n)\),辅助归并数组空间复杂度 \(\Theta(n)\)。
🐍 Python 求职机试标准实现(源自 practice_algorithms.html · 在线阅读器)
def count_inversions(nums):
    """归并分治统计逆序对总数 (i < j 且 nums[i] > nums[j])。O(n log n) 时间。"""
    def sort_count(arr):
        if len(arr) < 2:
            return list(arr), 0
        mid = len(arr) // 2
        left, c1 = sort_count(arr[:mid])
        right, c2 = sort_count(arr[mid:])
        merged, cross, i, j = [], 0, 0, 0
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                merged.append(left[i])
                i += 1
            else:
                merged.append(right[j])
                cross += len(left) - i
                j += 1
        merged.extend(left[i:])
        merged.extend(right[j:])
        return merged, c1 + c2 + cross

    _, total = sort_count(nums)
    return total

9 · 公式速查(一页纸素材)

渐近符号O 上界 / Ω 下界 / Θ 同阶;最优算法 = 达到下界 Ω(f) 的 O(f) 算法
层次1 ≺ log n ≺ nᵃ ≺ 多项式 ≺ 指数 ≺ n!;log n = o(nᵃ);n^b = o(aⁿ)
分析法则循环=体×次数;顺序相加;if-else 取 max
Master\(T(n)=aT(n/b)+f\): 比较 f 与 \(n^{\log_b a}\) → 三情形(nᴱ / nᴱlog n / f(n))
递归经典Hanoi \(2^n-1\);Fib 递归 O(2ⁿ) / 递推 O(n);棋盘覆盖 \(\Theta(4^k)\)
算法阶二分 Θ(logn);归并/快排平均 Θ(nlogn);快排最坏 O(n²);Strassen \(n^{2.81}\);大整数乘 \(n^{1.59}\);最接近点对 Θ(nlogn);select Θ(n)
机器提速64 倍:2ⁿ→n+6,n²→8n;100 倍:n→100n,n²→10n,n³→4.64n,n!→n+6.64

10 · 自测(含历年真题)

判 断(历年卷风格)
  1. O(n) 的算法在相同规模下总比 O(2ⁿ) 的快。(Review 选择题变体)
  2. 时间复杂度只能描述最坏情况下算法的运行时间上界。
  3. 归并排序在基于比较的排序模型中渐近最优;计数排序等非比较排序在附加条件下可能突破此下界。
  4. 快速排序在任何输入下都是 O(n log n)。
💡 破题关键与第一步提示(遮答案自测用 · 答案)

破题切入点:✗——只保证 n 充分大后渐近意义(且“总是”太绝对,小规模常数因子可能反转)。(👉 先合上下方完整推导,自己动手算完关键步再核对踩分点!)

📝 完整规范推导与评分踩分点(答案)
  1. ✗——只保证 n 充分大后渐近意义(且“总是”太绝对,小规模常数因子可能反转)。
  2. ✗——时间复杂度可以讨论最坏、平均、最好或摊还情形;大 O 是渐近上界,并非“时间复杂度”一律等于最坏情况。
  3. ✓(仅限比较排序的最坏情况模型):比较排序下界 Ω(n log n),归并达到。
  4. ✗——最坏 O(n²)(已有序输入 + 固定划分元)。
计 算
  1. 解 \(X(n)=X(n/3)+1,\ X(1)=1,\ n=3^k\)。(答:\(1+\log_3 n\))
  2. 排序 \(4n^2,\ \log n,\ n!,\ 3^n,\ 20n,\ 2,\ n^{2/3}\)。(见 §3 例)
  3. 求最大最小元素的分治算法在 n=2ᵏ 时比较次数?(答:元素间比较 \(3n/2-2\) 次——分治两两比出胜负组再各比,优于朴素 2n−2;复习大纲点名的 Counting inversions 同属此类分治计数题。)

下一模块:动态规划与贪心——四步法、矩阵连乘/LCS/最大子段和/0-1背包/最优BST;贪心两要素、活动安排及正确性证明、贪心 vs DP 的分界(可拆背包 vs 0-1 背包)。

🎯 考场题型信号词 → 破题套路速查表 期末冲刺必背 · 信号词秒配定理与防扣分清单

考场上读题 10 秒内锁定“题干信号词”,直接调用中间列的定理模板作答,并在交卷前对照右列逐条排查高频扣分点。

🔍 题干信号词 / 设问特征 ⚡ 必用定理 / 核心递推式 / 标准破题套路 🚨 老师批改高频扣分点
比较两个函数渐近阶 / 证明 \(f(n)=O(g(n)),\,\Omega(g(n)),\,\Theta(g(n))\) 极限比值法 \(\lim_{n\to\infty} f(n)/g(n) = c\)(\(0 ⚠ 避坑:混淆上界 \(O\) 与紧确界 \(\Theta\);多项式 vs 指数 vs 对数阶排序出错(牢记 \((\log n)^k \ll n^\varepsilon \ll n^k \ll c^n \ll n!\))
机器提速 / 时间扩展为原来 \(k\) 倍求新最大可解规模 \(n'\) 列出等式 \(T(n') = k\cdot T(n)\)(若机器速度变为 \(k\) 倍且运行相同时间),对多项式 \(n^d\) 得 \(n'=k^{1/d}n\)(乘法扩张),对指数 \(2^n\) 得 \(n'=n+\log_2 k\)(加法扩张) ⚠ 避坑:把指数级 \(2^{n'}=k\cdot 2^n\) 错算成 \(n'=kn\)(审题看清“原规模为 \(n\)”还是“原耗时为 \(t\)”)
求解分治递归方程 \(T(n)=aT(n/b)+f(n)\) Master 定理三情形:比较 \(f(n)\) 与临界幂 \(n^{\log_b a}\) 是否存在多项式级差距 \(n^\varepsilon\);若仅差 \(\log^k n\) 因子(如 \(n^{\log_b a}\log^k n\)),用扩展情形 2 得 \(\Theta(n^{\log_b a}\log^{k+1} n)\) ⚠ 避坑:遇到 \(T(n)=2T(n/2)+n\log n\) 误套情形 3(\(\log n\) 小于任意 \(n^\varepsilon\),多项式意义下不大于 \(n^1\),应得 \(\Theta(n\log^2 n)\))
设计分治算法(如芯片测试、大整数 Karatsuba、最接近点对、逆序对、第 \(k\) 小元素) 严格按四步作答:① 子问题划分方式;② 递归求解与剪枝(減治还是分治);③ 合并策略(重点写清如何把合并耗时从 \(O(n^2)\) 降到 \(O(n)\));④ 列出 \(T(n)\) 并用主定理算阶 ⚠ 避坑:在试卷上直接写 C/Python 代码块(2021 年历史复习资料强调文字分步说明与复杂度推导;今年是否允许直接写代码,以本学期任课教师通知为准)
证明基于比较的排序或查找问题的时间下界 构造二叉决策树:\(n\) 个元素全排列共有 \(n!\) 个叶结点,树高 \(h\ge \lceil\log_2(n!)\rceil = \Omega(n\log n)\) ⚠ 避坑:漏写“每次比较最多产生 2 个分支,深度为 \(h\) 的二叉树至多有 \(2^h\) 个叶子”这一关键桥梁不等式