第66章 算法的时间和空间效率分析

第66章 算法的时间和空间效率分析

衡量一个算法"快不快、占不占内存",靠的不是跑一次看秒表,而是渐进复杂度——即输入规模 nn 变大时,运行时间/内存如何增长。八级要求"能分析排序、查找、树图遍历、搜索、分治、动态规划等各类算法的时间和空间复杂度,并据此优化"。

常见复杂度增长曲线

66.1 复杂度分析方法

66.1.1 时间复杂度分析方法

  1. 找出算法中的基本操作:赋值、比较、算术运算等与问题规模强相关的操作。
  2. 统计基本操作总执行次数,记为函数T(n)T(n),nn为输入规模。
  3. 简化:仅保留最高阶项、丢弃系数,得到时间复杂度。
算复杂度的三步口诀

① 找"基本操作"(比较/赋值/算术);② 数它随 nn 执行多少次,写成 T(n)T(n);③ 只留最高阶、丢掉常数系数。例:T(n)=3n2+100n+5⇒O(n2)T(n)=3n^2+100n+5 \Rightarrow O(n^2)。

66.1.2 空间复杂度分析方法

  1. 统计程序占用所有存储空间:输入、代码、常量、临时变量、数组、栈等。
  2. 提取随nn变化的存储开销,记S(n)S(n)。
  3. 简化得到空间复杂度,只保留最高阶项。

66.2 各类算法复杂度分析

66.2.1 排序算法

冒泡排序

  • 时间复杂度:平均、最坏O(n2)O(n^2);完全有序最优O(n)O(n)
  • 空间复杂度:O(1)O(1),仅交换临时变量

快速排序

  • 时间复杂度:平均O(nlog⁡n)O(n\log n);数组有序/逆序最坏O(n2)O(n^2)
  • 空间复杂度:O(log⁡n)O(\log n)(递归栈深度),最坏O(n)O(n)

归并排序

  • 时间复杂度:最优/平均/最坏统一O(nlog⁡n)O(n\log n)
  • 空间复杂度:O(n)O(n),需要辅助数组存储合并结果

66.2.2 查找算法

顺序查找

  • 时间:最好O(1)O(1),平均/最坏O(n)O(n)
  • 空间:O(1)O(1)

二分查找

  • 时间:最好O(1)O(1),平均/最坏O(log⁡n)O(\log n)
  • 空间:迭代O(1)O(1);递归O(log⁡n)O(\log n)(递归栈)

66.2.3 树遍历(前/中/后序)

  • 时间复杂度:O(n)O(n),每个节点访问一次
  • 空间复杂度:O(h)O(h),hh为树高;平衡树O(log⁡n)O(\log n),斜树O(n)O(n)

66.2.4 图遍历 DFS / BFS

  • 时间复杂度:O(V+E)O(V+E),VV顶点数,EE边数
  • 空间复杂度:O(V)O(V)(栈/队列、访问标记数组)

66.2.5 暴力深度/广度搜索(迷宫等)

  • 时间:O(bd)O(b^d),bb分支数,dd搜索深度
  • 空间:O(bd)O(b^d)

66.2.6 分治算法

递推式T(n)=k⋅T(n/k)+D(n)+C(n)T(n)=k \cdot T(n/k)+D(n)+C(n),归并排序是典型例子,时间O(nlog⁡n)O(n\log n)。

66.2.7 动态规划

  • 一维DP:时间O(n)O(n)
  • 二维DP(LCS、区间DP):时间O(n2)O(n^2)
  • Floyd图全最短路:O(n3)O(n^3) 空间可通过滚动数组从O(n2)O(n^2)压缩至O(n)O(n)

66.3 复杂度等级从快到慢

O(1)<O(log⁡n)<O(n)<O(nlog⁡n)<O(n2)<O(n3)<2nO(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(n^3) < 2^n

66.4 真题示例(根据数据范围选算法)

【GESP 八级风格·选择题变形】 某题输入规模 n≤100000n \le 100000,时限 1 秒(一般 1 秒约可安全执行 10810^8 次基本操作)。下列哪个做法最可能超时?

  • A. O(n)O(n) 遍历一遍
  • B. O(nlog⁡n)O(n \log n) 排序后处理
  • C. O(n2)O(n^2) 双重循环
  • D. O(nlog⁡n)O(n \log n) 用优先队列求最值

分析与解答: 估算各做法在 n=105n=10^5 时的操作数:

  • A:10510^5 次 —— 轻松通过;
  • B/C 中的 nlog⁡n≈105×17≈1.7×106n\log n \approx 10^5 \times 17 \approx 1.7\times10^6 次 —— 通过;
  • C:n2=1010n^2 = 10^{10} 次 —— 远超 10810^8,必然超时。

因此选 C。这也是八级"算法优化"章节的核心动机:把 O(n2)O(n^2) 优化到 O(nlog⁡n)O(n\log n) 往往就是"过与不过"的分界线。

附:读代码判复杂度的迷你练习

int cnt = 0;
for (int i = 1; i <= n; ++i) // 外层 n 次
for (int j = 1; j <= n; ++j) // 内层 n 次
cnt++; // 基本操作

双重嵌套、每层都与 nn 相关,总次数 n×n=n2n \times n = n^2,时间复杂度为 O(n2)O(n^2);只用了常数个变量,空间复杂度 O(1)O(1)。

别被"小常数"迷惑

复杂度只反映"增长趋势",不反映确切秒数。但竞赛里常数也不能无限大:同样 O(n2)O(n^2),内层若还带一个 O(n)O(n) 的子过程就会变成 O(n3)O(n^3)。分析时要把"循环里又套了什么"看清楚。


练习题

说明

下列选择题考查复杂度等级、常见算法复杂度与读代码判阶,参考答案默认折叠,点击"参考答案"展开。

1.(等级) 下列复杂度从快到慢(最优→最差)排列,正确的是?

  • A. O(n)<O(log⁡n)<O(n2)O(n) < O(\log n) < O(n^2)
  • B. O(log⁡n)<O(n)<O(n2)O(\log n) < O(n) < O(n^2)
  • C. O(n2)<O(n)<O(log⁡n)O(n^2) < O(n) < O(\log n)
  • D. O(n)<O(n2)<O(log⁡n)O(n) < O(n^2) < O(\log n)
参考答案

B。公认顺序:O(1)<O(log⁡n)<O(n)<O(nlog⁡n)<O(n2)<O(n3)<O(2n)O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(n^3) < O(2^n)。

2.(排序) 归并排序的时间复杂度是?

  • A. 平均 O(n2)O(n^2),最坏 O(n2)O(n^2)
  • B. 统一 O(nlog⁡n)O(n\log n)
  • C. 平均 O(nlog⁡n)O(n\log n),最坏 O(n2)O(n^2)
  • D. O(n)O(n)
参考答案

B。归并排序无论最好/平均/最坏都是 O(nlog⁡n)O(n\log n);冒泡才是平均/最坏 O(n2)O(n^2)。

3.(查找) 在已排序数组中用二分查找,时间复杂度是?

  • A. O(n)O(n)
  • B. O(log⁡n)O(\log n)
  • C. O(n2)O(n^2)
  • D. O(1)O(1)
参考答案

B。二分每次把搜索区间减半,最坏比较次数约 log⁡2n\log_2 n,故 O(log⁡n)O(\log n)。

4.(读代码) 下面代码的时间复杂度是?

for (int i = 0; i < n; ++i)
for (int j = i; j < n; ++j)
sum++;
  • A. O(n)O(n)
  • B. O(nlog⁡n)O(n\log n)
  • C. O(n2)O(n^2)
  • D. O(n3)O(n^3)
参考答案

C。内层循环次数随 i 从 nn 递减到 1,总次数 n+(n−1)+⋯+1=n(n+1)/2=O(n2)n+(n-1)+\dots+1 = n(n+1)/2 = O(n^2)。

5.(图遍历) DFS / BFS 遍历一个 VV 个顶点 EE 条边的图,时间复杂度是?

  • A. O(V)O(V)
  • B. O(E)O(E)
  • C. O(V+E)O(V+E)
  • D. O(V×E)O(V \times E)
参考答案

C。每个顶点和每条边都只访问常数次,故 O(V+E)O(V+E)。

SShowball OI知识手册