算法基础(五)——增长量级为什么我们只关心最高阶项
1. 定位导航如果只看精确表达式算法分析会变得非常复杂。例如某个算法的运行时间可以写成T(n)3n210n100 T(n) 3n^2 10n 100T(n)3n210n100这个表达式里有三部分3n23n^23n210n10n10n100100100问题是当n很大时哪一部分真正决定整体增长这就是增长量级要回答的问题。2. 概念术语术语定义举例增长量级输入规模变大时运行成本增长的级别n、n log n、n²最高阶项表达式中增长最快的项3n² 10n 100中的3n²低阶项增长速度低于最高阶项的部分10n常数项不随n变化的固定成本100常数因子乘在主要项前面的固定倍数3n²中的3渐近分析关注n趋向很大时的增长趋势把3n² 10n 100看作O(n²)关键澄清忽略常数项不是说它完全没有成本而是说它不影响长期增长趋势。忽略低阶项不是说它永远不重要而是在大规模输入下最高阶项更关键。忽略常数因子不是为了偷懒而是为了比较算法的增长级别。复杂度表达的是趋势不是精确运行时间。3. 为什么要看增长量级算法分析最关心的问题是输入规模 n 变大以后运行时间增长得有多快如果两个算法分别是A(n) 100n B(n) n²在小规模输入下B(n)可能看起来并不差。n100nn²101000100100100001000010001000001000000当n 10时n²甚至更小。但当n 1000时n²已经远远超过100n。这说明小规模下的表现可能会误导我们大规模下的增长趋势才更关键。4. 最高阶项为什么最重要看这个表达式T(n)3n210n100 T(n) 3n^2 10n 100T(n)3n210n100当n 1时3n23,10n10,100100 3n^2 3,\quad 10n 10,\quad 100 1003n23,10n10,100100这时候常数项100最大。当n 10时3n2300,10n100,100100 3n^2 300,\quad 10n 100,\quad 100 1003n2300,10n100,100100平方项开始占主导。当n 100时3n230000,10n1000,100100 3n^2 30000,\quad 10n 1000,\quad 100 1003n230000,10n1000,100100平方项已经远远大于其他项。所以随着n增大真正决定整体增长趋势的是最高阶项。从图中可以看出虽然3n² 10n 100和n²的数值不完全一样但它们的曲线形状是一类的都会按平方级别增长。5. 常数项、低阶项、常数因子为什么可以忽略5.1 常数项常数项不随输入规模变化。例如T(n)n100 T(n) n 100T(n)n100当n很小时100很明显。但当n 1,000,000时100几乎不影响整体趋势。5.2 低阶项低阶项增长速度比最高阶项慢。例如T(n)n2n T(n) n^2 nT(n)n2n随着n变大n²会远远超过n。5.3 常数因子常数因子反映的是固定倍数。例如3n² 和 n²它们相差 3 倍但增长形态一样都是平方增长。在比较增长量级时我们更关心线性增长、平方增长、对数增长、指数增长而不是固定的 2 倍、3 倍、10 倍。6. 动态推演n 变大后谁在主导增长下面这个动态图展示了T(n) 3n² 10n 100中三部分的占比变化。可以看到n很小时常数项和低阶项也有存在感n增大后3n²的占比快速提升输入规模足够大时整体趋势几乎由平方项决定。所以把3n210n100 3n^2 10n 1003n210n100简化成O(n2) O(n^2)O(n2)不是随便省略而是基于增长趋势的合理抽象。7. 三步法如何简化复杂度表达式简化复杂度表达式可以按三步走以T(n)3n210n100 T(n) 3n^2 10n 100T(n)3n210n100为例。第一步去掉常数项3n210n100⇒3n210n 3n^2 10n 100 \Rightarrow 3n^2 10n3n210n100⇒3n210n第二步去掉低阶项3n210n⇒3n2 3n^2 10n \Rightarrow 3n^23n210n⇒3n2第三步去掉常数因子3n2⇒n2 3n^2 \Rightarrow n^23n2⇒n2所以最终记作O(n2) O(n^2)O(n2)8. 数值例子n3n²10n100T(n) 3n² 10n 1003n² 占比13101001132.65%1030010010050060.00%1003000010001003110096.46%1000300000010000100301010099.66%当n 1时最高阶项占比很小。但当n 1000时最高阶项已经占据绝大部分。这就是为什么复杂度分析关心“足够大规模下”的增长趋势。9. 代码实践下面用 Python 直观看一下不同项的占比变化defanalyze_terms(n):term13*n*n term210*n term3100totalterm1term2term3return{n:n,3n²:term1,10n:term2,100:term3,total:total,3n²_ratio:term1/total,}fornin[1,10,100,1000,10000]:resultanalyze_terms(n)print(fn{result[n]:6}f3n²{result[3n²]:12}f10n{result[10n]:8}f100{result[100]:5}ftotal{result[total]:12}f3n²占比{result[3n²_ratio]:.2%})可能输出n1 3n²3 10n10 100100 total113 3n²占比2.65% n10 3n²300 10n100 100100 total500 3n²占比60.00% n100 3n²30000 10n1000 100100 total31100 3n²占比96.46% n1000 3n²3000000 10n10000 100100 total3010100 3n²占比99.66% n10000 3n²300000000 10n100000 100100 total300100100 3n²占比99.97%这个结果非常直观n越大最高阶项越接近整个表达式。10. 常见误区误区一忽略常数就说明常数不重要不是。常数在实际工程中仍然重要比如缓存命中、网络延迟、磁盘 IO、语言运行时差异都会影响实际耗时。复杂度分析只是先看增长趋势不是完全否定常数成本。误区二复杂度低的算法一定更快不一定。在小数据场景下常数更小的算法可能更快。例如小数组排序时插入排序有时比更复杂的排序方法更快。误区三只要同是 O(n²)性能就完全一样不对。n²和100n²属于同一增长量级但实际运行时间可能相差很大。复杂度用于做大方向判断工程落地仍然需要基准测试。误区四复杂度就是精确公式不是。复杂度是增长趋势的抽象不是精确运行时间公式。11. 现代延伸增长量级在工程系统里非常常见。场景为什么关注增长量级日志分析全表扫描随着日志量线性增长数据库 Join嵌套循环可能接近平方级增长搜索排序候选集扩大后排序成本增加推荐召回向量数量变大后需要近似检索图计算边数增长会明显影响遍历成本大模型推理序列长度增长会影响注意力计算成本比如注意力机制中序列长度变大时标准注意力的计算成本常常和序列长度的平方相关。也就是说序列从 1000 增加到 2000不只是多一倍而可能带来接近四倍的相关计算量。这就是增长量级在现代 AI 系统里非常重要的原因。12. 思考题为什么3n² 10n 100可以简化为O(n²)为什么小数据场景下低复杂度算法不一定最快如果一个算法是100n另一个算法是n²在哪些规模下后者可能更快常数因子在工程优化中有没有意义和复杂度分析的关系是什么你在工作中见过哪些“数据量一大就变慢”的功能它可能属于什么增长量级13. 本篇小结本篇的核心结论是复杂度分析关注的是增长趋势最高阶项决定大规模输入下的主要增长形态常数项、低阶项、常数因子通常不改变增长量级3n² 10n 100可以抽象成O(n²)这种抽象不是为了偷懒而是为了更清楚地比较算法在大规模输入下的表现。下一步就可以继续学习更正式的复杂度记号比如O、Ω、Θ它们分别用于描述不同角度的增长边界。