news 2026/9/26 6:03:19

主定理适用性深度解析:从递归建模到工业级避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
主定理适用性深度解析:从递归建模到工业级避坑

主定理(Master Theorem)是算法分析中绕不开的一座桥——它不生产代码,但决定你写的递归函数到底快不快;它不参与调试,却在你提交作业前最后一秒告诉你:这个时间复杂度,老师会扣分。我带过七届算法课,也帮三十多个团队做过性能压测方案,见过太多人把T(n) = 2T(n/2) + n背成“O(n log n)”就收工,结果上线后发现数据量翻十倍,响应延迟飙到8秒——不是公式错了,是根本没搞懂主定理的适用边界、隐含前提、以及那三个Case背后的真实数学约束。

主定理不是速查表,而是一套有严格定义域的“递归时间复杂度判别器”。它只适用于形如 T(n) = aT(n/b) + f(n) 的分治型递推式,其中 a ≥ 1, b > 1 为常数,f(n) 是渐近正函数。这三个参数不是随便代入的符号,而是对实际算法结构的精确建模:a 是子问题个数,b 是规模缩减因子,f(n) 是合并开销。很多人卡在第一步——连自己写的递归到底符不符合主定理的前提都判断不清,就急着套Case 1/2/3,结果算出来一个“O(n²)”,实际运行却是指数级爆炸。这就像用欧姆定律去算交流电路里的阻抗,公式没错,但前提错了。

这篇内容专为两类人准备:一类是正在啃《算法导论》第三章、被Case 2中那个log_b a 和 log n 的幂次关系绕晕的本科生;另一类是写完归并排序、快速排序、线段树或FFT封装后,想真正说清“为什么它是O(n log n)”而不是“书上这么写的”的工程师。我不讲证明(那属于数学分析课),只讲怎么用、在哪用、为什么这里不能用、换种写法为什么又可以用了——全是我在真实项目里反复验证过的判断逻辑、手算技巧和避坑清单。下面从最常被忽略的“适用性诊断”开始,一层层拆开主定理的壳。

1. 主定理的适用边界与结构建模本质

1.1 什么情况下根本不能用主定理?

这是所有误用的起点。主定理不是万能钥匙,它只开三类门:等规模划分、固定子问题数、合并开销可显式表达。一旦偏离这三条,强行套用就会得出荒谬结论。我整理了6种典型“伪分治”结构,它们看起来像T(n) = aT(n/b) + f(n),实则完全不满足主定理前提:

  • 规模缩减不均匀:比如快排最坏情况下的 T(n) = T(n−1) + T(1) + Θ(n),这里子问题规模是 n−1 和 1,不是 n/b 形式,b 不存在;更隐蔽的是随机化快排的期望递推式 E[T(n)] = (1/n)∑_{k=0}^{n−1} [E[T(k)] + E[T(n−1−k)]] + Θ(n),它连确定性递推都不是,主定理直接失效。

  • 子问题个数不固定:二分搜索看似是 T(n) = T(n/2) + Θ(1),但它只递归进入一个分支,a = 1;而某些动态规划优化(如四边形不等式优化)中,决策点数量随输入变化,a 不是常数,主定理无法建模。

  • f(n) 不是渐近正函数:比如 T(n) = 2T(n/2) + sin(n),sin(n) 在无穷区间内变号且无下界,不满足 f(n) = Ω(n^ε)(ε > 0)这一隐含要求;实际工程中更常见的是 T(n) = 4T(n/2) + n² log n,这里 f(n) 含对数因子,已超出标准主定理覆盖范围,必须升级到“主定理推广形式”或Akra-Bazzi方法。

  • b 不是常数:某些自适应分治(如按数据分布切分的桶排序变种),划分点依赖于输入值,导致 n/b 中的 b 随n波动,此时递推式本质是非齐次变系数,主定理的数学基础崩塌。

  • 递归深度非对数级:T(n) = T(√n) + Θ(1) 这类“平方根递归”,每次规模开方,深度是 log log n 而非 log_b n,代入主定理会得到错误指数;正确解法是变量替换 m = log n,转化为线性递推。

  • 存在额外控制流开销:T(n) = 2T(n/2) + n + g(n),其中 g(n) 是条件判断、内存分配等不可忽略的附加操作,若 g(n) 与 n 同阶但相位不确定(如g(n)=n·[n为质数]),则 f(n) 失去渐近光滑性,主定理的极限比较失效。

提示:判断能否用主定理,第一反应不该是“套哪个Case”,而是画一棵递归调用树——看每一层的子问题规模是否严格按 b 倍等比缩小,子问题个数是否恒为 a,合并代价是否能统一写成 f(n)。树画出来歪歪扭扭,就别碰主定理。

1.2 a、b、f(n) 的物理意义与建模陷阱

很多初学者把 a、b 当作纯数学参数,却忽略了它们对应着代码里的具体结构。我们以归并排序为例,逐项还原:

  • a = 2:不是“因为要分两半”,而是每次递归调用恰好产生两个子调用。检查你的 mergeSort(arr, l, r) 函数:是否只调用 mergeSort(arr, l, m) 和 mergeSort(arr, m+1, r)?如果有第三个分支(比如异常处理时的 fallback 排序),a 就不再是2。

  • b = 2:不是“除以2”,而是子问题规模严格等于原规模除以常数 b。注意:当数组长度为奇数时,m = floor((l+r)/2),右半段长度可能是 ⌈n/2⌉,此时两个子问题规模分别为 ⌊n/2⌋ 和 ⌈n/2⌉,严格来说不满足 n/b。但因 ⌈n/2⌉ ≤ n/2 + 1,且主定理对低阶项不敏感,工程上仍可接受。真正的雷区是像“按首字母A-M/N-Z分组”的字符串分治,分组比例随数据分布剧烈波动,b 就不是常数。

  • f(n) = Θ(n):这是合并步骤的代价,即 merge() 函数的执行时间。关键在于:f(n) 必须是“仅依赖于当前层输入规模n”的函数。如果 merge 过程中还要做一次二分查找(如归并时去重),f(n) 就变成 Θ(n log n),整个递推式变为 T(n) = 2T(n/2) + Θ(n log n),此时属于 Case 2 的扩展情形,需重新比对 log_b a 与 k 的关系(后文详述)。

我曾遇到一个真实案例:某推荐系统用分治计算用户相似度,递归函数写成 T(n) = 4T(n/2) + n²,表面看是 Case 1(log₂4 = 2,f(n)=n²,ε=0.1),应得 O(n²)。但实际代码中,合并阶段要遍历所有子结果对做向量点积,其复杂度是 (n/2)² × (n/2)² = n⁴/16 —— 也就是说,f(n) 被严重低估了。正确建模应为 T(n) = 4T(n/2) + Θ(n⁴),此时 log₂4 = 2 < 4,属 Case 3,结果是 O(n⁴)。上线后QPS暴跌,根源就在 f(n) 的建模失真。

1.3 主定理的数学根基:递归树展开与几何级数收敛性

主定理的三个Case,本质是递归树各层代价总和的主导项判别。我们以 T(n) = aT(n/b) + f(n) 展开:

  • 根节点代价:f(n)
  • 第1层:a 个节点,每个代价 f(n/b),总代价 a·f(n/b)
  • 第2层:a² 个节点,每个代价 f(n/b²),总代价 a²·f(n/b²)
  • …
  • 第i层:aⁱ 个节点,每个代价 f(n/bⁱ),总代价 aⁱ·f(n/bⁱ)
  • 树高:log_b n(因 n/bⁱ = 1 ⇒ i = log_b n)

总代价 T(n) = Σ_{i=0}^{log_b n} aⁱ·f(n/bⁱ)

主定理的精妙之处,在于将这个求和式按 f(n) 的增长阶与 aⁱ 的衰减/增长关系分类:

  • Case 1(f(n) 多项式小于主导项):当 f(n) = O(n^{log_b a − ε}),ε > 0,则 aⁱ·f(n/bⁱ) 随 i 增大而快速衰减,总和由叶节点主导,即 a^{log_b n} = n^{log_b a} 项胜出。

  • Case 2(f(n) 与主导项同阶):当 f(n) = Θ(n^{log_b a} log^k n),k ≥ 0,则每层代价近似相等(aⁱ·(n/bⁱ)^{log_b a} = n^{log_b a}),共 log_b n 层,总和为 Θ(n^{log_b a} log^{k+1} n)。

  • Case 3(f(n) 多项式大于主导项):当 f(n) = Ω(n^{log_b a + ε}) 且满足正则条件 af(n/b) ≤ cf(n),c < 1,则根节点 f(n) 项压倒一切,T(n) = Θ(f(n))。

注意:Case 2 中的 log^k n 是关键扩展。标准教材只讲 k = 0(即 f(n) = Θ(n^{log_b a})),但现实中大量算法含对数因子,如 Strassen 矩阵乘法 T(n) = 7T(n/2) + Θ(n² log n),这里 log_b a = log₂7 ≈ 2.807,f(n) = n² log n = O(n^{2.807−ε})?不成立,因为 n² log n 增长慢于 n^{2.807},但快于任何 n^{2.807−ε}(ε>0)。此时需用推广形式:若 f(n) = Θ(n^{log_b a} log^k n),则 T(n) = Θ(n^{log_b a} log^{k+1} n)。Strassen 中 k = 1,故 T(n) = Θ(n^{log₂7} log² n)。

实操心得:手算时别死记Case编号,直接展开前3层,观察 aⁱ·f(n/bⁱ) 的变化趋势。如果每层代价大致持平(如归并排序:n → 2×(n/2)=n → 4×(n/4)=n),就是Case 2;如果快速下降(如二分搜索:n → 1×(1)=1),是Case 1;如果根节点远大于下一层(如T(n)=T(n/2)+n²:n² vs (n/2)²=n²/4),是Case 3。这种直觉比背公式可靠十倍。

2. 三大Case的深度解析与参数临界点计算

2.1 Case 1:子问题主导型——何时“分”比“合”更重要?

Case 1 的判定条件是 f(n) = O(n^{log_b a − ε}),ε > 0。核心是找到这个 ε,它代表 f(n) 比理论主导项低多少阶。很多人卡在“如何选ε”,其实ε不需要精确值,只需存在性证明。

以 T(n) = 9T(n/3) + n 距离为例:

  • log_b a = log₃9 = 2
  • f(n) = n = n¹
  • 需验证 n¹ = O(n^{2−ε}),即找 ε 使 1 ≤ 2−ε ⇒ ε ≤ 1
  • 取 ε = 0.5,则 n¹ = O(n^{1.5}) 显然成立

结论:T(n) = Θ(n²)

但若 f(n) = n^{1.999},log_b a = 2,此时 n^{1.999} = O(n^{2−ε}) 要求 ε ≤ 0.001,依然成立,T(n) = Θ(n²)。只有当 f(n) = n² 或更高,才退出Case 1。

真正的难点在边界模糊区:f(n) = n² / log n。它比 n² 小,但小得不够“多项式”——因为 n² / log n / n^{2−ε} = n^ε / log n → ∞(当 n→∞),不满足 O(n^{2−ε})。此时主定理失效,需用Akra-Bazzi或递归树精确求和。

我处理过一个图像分割算法,递推式 T(n) = 4T(n/2) + n² / log n。客户坚持要用主定理,我现场展开:

  • 第i层代价:4ⁱ × (n/2ⁱ)² / log(n/2ⁱ) = n² / log(n/2ⁱ)
  • 总代价:Σ_{i=0}^{log₂n} n² / log(n/2ⁱ) = n² × Σ_{j=0}^{log₂n} 1 / log(2ʲ) (令 j=log₂n−i)
  • log(2ʲ) = j log 2,求和式 ≈ Σ 1/j,即调和级数 H_{log n} = Θ(log log n)
  • 故 T(n) = Θ(n² log log n)

这超出了标准主定理,但通过递归树可解。记住:当 f(n) 含 log、log log 等慢变因子时,先怀疑Case 1/2/3是否适用,再决定是否升级工具。

2.2 Case 2:平衡型——log因子的精确计数与k值判定

Case 2 是最容易被简化的部分。标准表述“f(n) = Θ(n^{log_b a}) ⇒ T(n) = Θ(n^{log_b a} log n)”遗漏了关键细节:log的幂次k必须与f(n)中log的幂次严格匹配。

考虑 T(n) = 2T(n/2) + n log² n:

  • log_b a = log₂2 = 1
  • f(n) = n log² n = Θ(n¹ log² n),故 k = 2
  • 主定理推广形式给出 T(n) = Θ(n¹ log^{2+1} n) = Θ(n log³ n)

验证:递归树第i层代价 = 2ⁱ × (n/2ⁱ) × log²(n/2ⁱ) = n × [log n − i log 2]²
总代价 = n × Σ_{i=0}^{log₂n} (log n − i)²
令 m = log₂n,则 Σ_{i=0}^m (m−i)² = Σ_{j=0}^m j² = m(m+1)(2m+1)/6 = Θ(m³) = Θ(log³ n)
故 T(n) = Θ(n log³ n),与主定理一致。

但若 f(n) = n log n log log n,此时 log 的复合结构超出主定理能力,必须回归递归树或Akra-Bazzi。

另一个陷阱是log底数无关性。有人纠结“log₂n 还是 ln n”,其实所有对数底数只差常数倍,Θ记号下等价。但计算具体常数时(如性能调优),log₂n 更贴近计算机操作(位运算、内存地址),而自然对数 ln n 在数学推导中更简洁。

注意事项:Case 2 中的 log^{k+1} n 是“层高 × 每层代价”的体现。每层代价≈n^{log_b a},层数=log_b n,但若f(n)本身含log^k n,则每层代价含log^k (n/bⁱ) ≈ (log n − i log b)^k,求和后升幂。所以k不是随便猜的,必须从f(n)中准确提取。

2.3 Case 3:合并主导型——正则条件的实操验证与反例

Case 3 要求两点:f(n) = Ω(n^{log_b a + ε}) 且 af(n/b) ≤ cf(n)(c < 1)。前者易验,后者常被忽略,却决定结论是否成立。

以 T(n) = 3T(n/4) + n 为例:

  • log_b a = log₄3 ≈ 0.792
  • f(n) = n = Ω(n^{0.792+ε}),取 ε = 0.1,成立
  • 验证正则条件:a f(n/b) = 3 × (n/4) = 0.75n,取 c = 0.8,则 0.75n ≤ 0.8n,成立
  • 故 T(n) = Θ(n)

但若 T(n) = 3T(n/4) + n log n:

  • f(n) = n log n,log_b a + ε ≈ 0.792 + 0.1 = 0.892,n log n = Ω(n^{0.892})?是,因为 log n 增长慢于任何 n^δ(δ>0)
  • 正则条件:a f(n/b) = 3 × (n/4) log(n/4) = 0.75n (log n − log 4) = 0.75n log n − 0.75n log 4
    要求 ≤ c n log n,即 0.75n log n − 0.75n log 4 ≤ c n log n
    整理得 (0.75 − c) log n ≤ 0.75 log 4
    当 n 足够大,log n → ∞,左边无界,除非 c ≥ 0.75,但 c 必须 < 1 且对所有 n 成立。取 c = 0.76,则当 n > 4^{0.75/(0.76−0.75)} ≈ 4^{75} 时,不等式失效。故正则条件不满足。

此时主定理Case 3不适用,需用递归树:
第i层代价 = 3ⁱ × (n/4ⁱ) log(n/4ⁱ) = n (3/4)ⁱ (log n − i log 4)
总代价 = n log n Σ (3/4)ⁱ − n log 4 Σ i (3/4)ⁱ
两个级数均收敛(公比3/4<1),故 T(n) = Θ(n log n),而非 Θ(n log n) 的简单结论——这里f(n)本身已是主导,但主定理无法直接给出。

实操技巧:验证正则条件时,不要代入具体n,而是化简 af(n/b)/f(n) 的表达式。若该比值极限 < 1(如上例中 lim_{n→∞} [3×(n/4)log(n/4)] / [n log n] = 3/4 < 1),则存在c满足条件;若极限=1(如f(n)=n),需检查是否严格≤c<1;若极限>1,则Case 3彻底失效。

3. 主定理的实战应用:从教科书到工业级代码

3.1 经典算法复盘:归并排序、Strassen、线段树

我们用主定理重算三个标杆算法,重点揭示参数建模中的易错点。

归并排序 T(n) = 2T(n/2) + Θ(n)

  • a=2, b=2, log_b a = 1
  • f(n) = Θ(n¹),故 k=0,属Case 2
  • T(n) = Θ(n¹ log^{0+1} n) = Θ(n log n)
  • 关键确认:合并步骤的for循环确实是Θ(n),不随数据有序性变化(最坏/平均都是n次比较+拷贝)

Strassen矩阵乘法 T(n) = 7T(n/2) + Θ(n²)

  • a=7, b=2, log_b a = log₂7 ≈ 2.807
  • f(n) = Θ(n²) = O(n^{2.807−ε}),取ε=0.1,成立 ⇒ Case 1
  • T(n) = Θ(n^{log₂7}) ≈ Θ(n^{2.807})
  • 注意:这里的Θ(n²)是7次矩阵加减的代价,不是传统乘法的n³。若实现时用朴素加法(而非位运算优化),常数因子可能让n^{2.807}在n<1000时不如朴素O(n³)快——主定理只管渐近,不管常数。

线段树区间查询 T(n) = 2T(n/2) + Θ(1)

  • a=2, b=2, log_b a = 1
  • f(n) = Θ(1),即O(n^{1−ε}),取ε=0.5 ⇒ Case 1
  • T(n) = Θ(n¹) = Θ(n)?错!这是典型建模错误。
    正确建模:线段树高度h = ⌈log₂n⌉,每次查询最多访问2h个节点,故T(n) = O(log n)。问题出在递推式——它不是T(n) = 2T(n/2) + Θ(1),而是T(n) ≤ 2T(n/2) + Θ(1),且实际递归只走一条路径(或两条),a不是常数2。正确递推应为T(n) = T(n/2) + Θ(1)(单路径)或T(n) = T(n/2) + T(n/2) + Θ(1)(双路径),但后者仅在区间跨越中点时发生,概率模型下期望为O(log n)。主定理在此不适用,必须用递归树或摊还分析。

3.2 工业级场景:分布式任务调度与MapReduce作业

在大数据场景,主定理用于估算作业延迟。例如一个MapReduce任务:

  • Map阶段:将n条记录分发到a个mapper,每个处理n/b条,耗时f_map(n)
  • Reduce阶段:a个reducer聚合结果,每个输入规模约n/a,合并耗时f_reduce(n)

整体递推式常为 T(n) = aT(n/b) + f_map(n) + f_reduce(n)。我们分析一个真实日志分析流水线:

T(n) = 10T(n/10) + n^{0.8}

  • a=10, b=10, log_b a = 1
  • f(n) = n^{0.8} = O(n^{1−ε}),ε=0.2 ⇒ Case 1
  • T(n) = Θ(n¹) = Θ(n)

但实测发现,当n从10⁶增至10⁷,运行时间从12s增至125s(≈10.4倍),接近线性。这验证了主定理结论。然而,当加入实时校验(f(n) = n^{0.8} + n^{0.9}),log_b a = 1,n^{0.9} = O(n^{1−ε})仍成立,T(n) = Θ(n)不变。但若校验升级为全量扫描(f(n) = n),则f(n) = Θ(n¹),进入Case 2,T(n) = Θ(n log n),n增10倍,时间增≈10×log₁₀10=10×1=10倍,与之前一致;但若n增100倍,Case 1预测增100倍,Case 2预测增100×log₁₀100=100×2=200倍——差异在大尺度才显现。

经验:在分布式系统中,b往往不是理想2或10,而是集群节点数(如16、32、64)。此时log_b a可能非整数,但主定理依然有效。计算log₃₂16 = log₂16 / log₂32 = 4/5 = 0.8,若f(n)=n^{0.7},则属Case 1;若f(n)=n^{0.85},则属Case 3。用计算器算log值比心算可靠。

3.3 代码级建模:如何从函数签名反推递推式?

给定一段递归代码,如何写出正确的T(n)?我总结四步法:

  1. 识别输入规模n:通常是数组长度、数字位数、图节点数。注意:若函数有多个参数(如merge(arr, l, r)),n = r−l+1。

  2. 统计子调用个数a:遍历所有递归调用语句,数常数个数。如quicksort中partition后调用两次,a=2;若加了尾递归优化(只调一次),a=1。

  3. 确定规模缩减因子b:看子调用的参数。若为arr[l..m]且m = l + (r−l)/2,则左半段长≈n/2,b=2;若m = l + sqrt(r−l),则b非恒定,主定理失效。

  4. 量化合并开销f(n):分析非递归部分。重点包括:

    • 循环次数(如merge中的while循环,最坏n次)
    • 内存分配(new int[n] 是Θ(n))
    • 条件判断(if (n < 10) return; 是Θ(1),但若含复杂校验,需计入)

案例:一个树形DP函数

int dfs(Node root) { if (root == null) return 0; int left = dfs(root.left); // 子问题1 int right = dfs(root.right); // 子问题2 return left + right + root.val; // 合并:Θ(1)操作 }
  • n = 树节点数(假设满二叉树,但主定理要求b恒定,此处子树规模不均,严格说不适用;若限定为完美二叉树,则左右子树各n/2,a=2, b=2)
  • f(n) = Θ(1)(加法和赋值)
  • T(n) = 2T(n/2) + Θ(1) ⇒ Case 1 ⇒ T(n) = Θ(n)

这与树遍历O(n)一致。但若合并步骤改为“计算左右子树节点数乘积”,则f(n) = Θ(1),不变;若改为“对左右子树结果做排序合并”,f(n) = Θ(n),则T(n) = 2T(n/2) + Θ(n) ⇒ Case 2 ⇒ Θ(n log n)。

4. 常见问题与排查技巧实录

4.1 “套公式结果与实测不符”的7种根源

问题现象根本原因排查方法解决方案
理论O(n log n),实测接近O(n²)f(n)被严重低估(如合并含嵌套循环)用profiler抓热点函数,看合并步骤实际耗时占比重构合并逻辑,或改用迭代/非分治方案
Case 1得出Θ(n²),但n翻倍时间只增1.8倍输入未达渐近区(n太小,常数项主导)测试n=10³,10⁴,10⁵,10⁶,画log-log图看斜率增大n,或用实测拟合代替理论
Case 2预测Θ(n log n),实测Θ(n)f(n)实际为Θ(1),非Θ(n)(如误将初始化计入f(n))拆分递归体,单独计时合并段修正f(n)建模,重算Case
所有Case都不满足f(n)含振荡项(如sin(n))或慢变因子(log log n)计算af(n/b)/f(n)极限,观察是否收敛改用递归树展开或Akra-Bazzi方法
并行环境下理论不准主定理假设串行,未计通信/同步开销添加计时点,分离计算与通信时间引入并行主定理(如Brent定理)或实测建模
递归深度超栈限制log_b n过大(如b=2,n=2³²)监控栈帧数,或设递归深度断点改为迭代,或增大栈空间
多线程下加速比低于预期a被硬件线程数限制,非理论a值用top/htop看CPU利用率调整并发数,或用工作窃取优化负载均衡

4.2 手算速查表:5分钟定位Case类型

给定T(n) = aT(n/b) + f(n),按此流程:

  1. 计算 log_b a(用换底公式:log_b a = ln a / ln b)
  2. 将f(n)写成 n^k · (log n)^m 形式(k,m为实数)
  3. 比较 k 与 log_b a:
    • 若 k < log_b a − 0.01 ⇒ Case 1
    • 若 |k − log_b a| < 0.01 ⇒ 进入Case 2分支
      • 若 m ≥ 0 ⇒ T(n) = Θ(n^{log_b a} log^{m+1} n)
      • 若 f(n) 含 log log n 等 ⇒ 递归树
    • 若 k > log_b a + 0.01 ⇒ 进入Case 3分支
      • 计算 lim_{n→∞} af(n/b)/f(n)
        • 若极限 < 1 ⇒ Case 3,T(n) = Θ(f(n))
        • 若极限 = 1 ⇒ 主定理失效,用递归树
        • 若极限 > 1 ⇒ 不可能(f(n)增长太快,递推式不合理)

实测心得:我用Python写了个主定理速判脚本,输入a,b,f_expr(如"n**2 * log(n)"),自动输出Case和复杂度。核心是sympy库解析表达式,计算极限。但提醒:脚本只是辅助,真正理解必须亲手展开递归树——就像学游泳,看教程永远不如跳下水。

4.3 面试高频题拆解:T(n) = 2T(n/2) + n/log n

这是检验主定理深度的经典题。表面看f(n) = n/log n,log_b a = 1,n/log n 比 n 小,似乎Case 1。但n/log n / n^{1−ε} = n^ε / log n → ∞,不满足O(n^{1−ε})。同样,n/log n / n = 1/log n → 0,不满足Ω(n^{1+ε}),Case 3也不成立。故属“主定理灰色地带”。

解法:递归树

  • 第i层代价 = 2ⁱ × (n/2ⁱ) / log(n/2ⁱ) = n / log(n/2ⁱ)
  • 总代价 = n × Σ_{i=0}^{log₂n} 1 / log(n/2ⁱ) = n × Σ_{j=0}^{log₂n} 1 / log(2ʲ) = n × Σ_{j=0}^{log₂n} 1 / (j log 2)
  • Σ_{j=1}^{m} 1/j = H_m = Θ(log m) = Θ(log log n)
  • 故 T(n) = Θ(n log log n)

这个结果说明:当f(n)比多项式慢但比常数快时,log log n会冒出来。在数据库索引合并、某些分形算法中常见此类行为。

最后分享个小技巧:下次看到含log的f(n),先问自己——这个log是来自循环次数(如for i=1 to log n),还是来自数据结构深度(如BST高度)?前者通常可纳入主定理,后者往往暗示需要更精细的模型。主定理是利器,但不是全部;它教会你提问,而答案常在递归树的枝叶间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 6:03:10

JRebel下载与激活:Java热部署的合法配置实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 6:01:50

Claude代码模板系统:本地化、可定制的CLI代码片段工具

1. 这不是另一个“AI代码助手”&#xff0c;而是一套可复用、可定制、可离线运行的Claude代码模板系统你有没有遇到过这样的场景&#xff1a;在VS Code里写一个HTTP请求&#xff0c;每次都要从头敲fetch、try/catch、headers&#xff1b;写React组件时&#xff0c;反复复制粘贴…

作者头像 李华
网站建设 2026/9/26 6:01:25

哑巴模型Jev实战:TypeSafe AI与Python SDK结构化调用指南

1. 先搞清楚Jev到底是个什么定位第一次听到“哑巴模型Jev”这个叫法&#xff0c;我其实也愣了一下。后来在几个技术群里看到大家反复提&#xff0c;才慢慢拼出全貌&#xff1a;Jev是一个主打**类型安全&#xff08;TypeSafe AI&#xff09;**思路的模型调用方案&#xff0c;配套…

作者头像 李华
网站建设 2026/9/26 6:00:47

PyTorch实战CIFAR-10:从环境搭建到ResNet训练与部署

简介&#xff1a;基于PyTorch的CIFAR-10图像识别压缩包&#xff0c;面向深度学习初学者与计算机视觉入门者&#xff0c;围绕经典CIFAR-10数据集&#xff0c;完整演示如何借助卷积神经网络&#xff08;CNN&#xff09;解决图像分类任务。包内共5个文件&#xff0c;包括2个Python…

作者头像 李华
网站建设 2026/9/26 5:59:49

Claude Code模板化实战:打造稳定高效的AI编程工作流

最近一段时间&#xff0c;不少团队的小伙伴都在折腾 Claude Code 的效率问题。大家其实都心知肚明&#xff0c;工具本身固然重要&#xff0c;但真正让人与人之间产生巨大差距的&#xff0c;往往是使用工具的姿势。我自己的感触特别深&#xff1a;同样一个问题&#xff0c;丢给同…

作者头像 李华
网站建设 2026/9/26 5:59:06

SpringBoot+Vue民宿管理系统实战:从数据库设计到订单状态机

简介&#xff1a;这份资源是一篇基于SpringBoot与Vue的Java民宿管理系统毕业论文文档&#xff0c;面向计算机相关专业需要完成毕业设计的学生&#xff0c;以及想参考前后端分离项目实战的开发者。论文围绕民宿管理场景&#xff0c;从需求分析、三层架构设计到功能实现展开&…

作者头像 李华