正在阅读Hello 算法 2.3 精讲:时间复杂度,从数清一次执行到理解增长规律
香港天气加载中…
QWeather ↗
← Back to the notebook

Hello 算法 2.3 精讲:时间复杂度,从数清一次执行到理解增长规律

把 n、操作计数、O/Ω/Θ、常见增长阶与最好/最坏/平均情况逐层拆开,用可运行示例推导循环和递归成本,澄清原书的符号简化与 Python 隐藏工作。

阅读主线:时间复杂度不是代码需要几秒

时间复杂度描述:在明确输入规模、输入情况和计算模型后,完成任务的工作量如何随规模增长。它不是运行秒数,也不是代码行数。先把一次执行数清楚,再把不同规模的成本写成函数,最后讨论增长边界。

本节对应原书 2.3.1—2.3.5:增长趋势、渐近上界、推算方法、七类常见增长、最好/最坏/平均情况。下面按阅读依赖重新组织,补充 Python 机制、符号边界与原创例子。已有循环和递归笔记保留原路径,本篇作为复杂度分析总入口,不重复发布那些文章。

文章较长,建议分三次阅读:先读“规模与符号”,再读“循环与递归”,最后读“输入情况与误区”。每段代码独立运行,不依赖前面片段留在解释器里的变量。

知识点 01—02:n 到底是什么,为什么要先说清楚

n 通常表示输入规模,但不是固定含义。查找列表时,n 可以是列表元素个数;处理字符串时,可以是字符个数;处理图时,常常需要顶点数 V 与边数 E 两个量。变量名碰巧叫 n,不足以说明规模。

问题规模定义不能混同的量
在列表里找目标n = len(values)元素值大小不等于元素个数
比较两批记录的所有配对n 条与 m 条精确次数为 nm;未说明 n、m 的关系,不能直接改成单变量平方阶
处理一个整数 N可能按数值 N,也可能按表示位数 bN 次操作可能是相对 b 的指数级

例如正整数 N 的二进制位数 b 满足 2b−1 ≤ N < 2b。如果某算法重复 N 次,写成 Θ(N) 是相对数值的结论;相对位数 b,N 可达到指数级。后续代码中的 n 默认是非负普通整数规模,或由有限列表长度得到;分治示例的规模也是元素数量,不是数据值。

知识点 03:数什么工作,要先选一把尺

可以数比较次数、循环体执行次数、访问多少元素,或把固定的一组动作视为一个单位。不同计数口径的精确公式可能不同,只要基本动作成本有固定上界且不能被忽略,通常可得到一致的增长阶。

本节先采用常用单位成本模型:普通列表索引、固定大小数值的比较和算术等基本操作,按固定成本计;循环体中若复制列表、遍历容器或生成输出,必须展开。计数器的值表示选定的工作次数,不是纳秒,也不声称包含解释器所有指令。

Python 整数支持任意精度,因此极长整数加法、乘法、除法或比较的成本也与位数有关。下面的次数公式仍可准确描述执行了多少次指定动作,但不应无条件升级为任意长整数上的完整位运算复杂度。原书开篇给操作标注纳秒,是教学设定,不是本机测量;本篇不沿用这些数值作性能数据。

机制依据:Python 数值类型:整数精度。实际计时与模型的区别见 2.1 实测与理论估算。

知识点 04:常数阶不等于只做一次,也不等于一定快

下面分别固定执行 17 次、按输入执行 n 次、执行 n×n 次。输出记录的是各自循环体里加 1 的次数。n 改变时,第一个函数的工作不变,第二个成比例增长,第三个按平方增长。

固定次数即使改成一百万,仍与 n 无关;它可能比小输入上的线性循环更慢。“常数阶”强调相对 n 不增长,不保证耗时很小。n=0 时后两个计数为 0,但调用和判断并没有消失,不能据此说实际程序耗时为零。

Python
def fixed_work(n):
    count = 0
    for _ in range(17):
        count += 1
    return count

def linear_work(n):
    count = 0
    for _ in range(n):
        count += 1
    return count

def square_work(n):
    count = 0
    for _ in range(n):
        for _ in range(n):
            count += 1
    return count

for n in (0, 1, 4, 8):
    print(n, fixed_work(n), linear_work(n), square_work(n))

知识点 05:O 是增长上界,不是精确次数

设 T(n) 是已选定口径下的非负成本函数。T(n)=O(f(n)) 表示:可以找到固定正数 c 和一个阈值 n₀,使所有 n ≥ n₀ 都满足 T(n) ≤ c·f(n)。c 不能随 n 任意改变;f(n) 在讨论范围内为正。这里的等号是渐近记法的惯用写法,不是说 O(f(n)) 是一个具体数值。

例如 T(n)=4n+9。n≥1 时 9≤9n,所以 4n+9≤13n。取 c=13、n₀=1,便给出了 O(n) 的有效上界。下表的程序只是核对几个数,不构成对所有 n 的证明;证明来自上面的不等式。

“渐近”表示关注足够大规模以后的增长关系,不要求每一个小输入都满足同一个未经缩放的大小比较。因为 n≤n²(n≥1),线性成本也属于 O(n²),只是这个上界不紧。O 并不自动意味着“恰好同阶”。

Python
for n in (1, 2, 4, 8):
    exact = 4 * n + 9
    upper = 13 * n
    print(n, exact, upper, exact <= upper)

知识点 06:Ω 是下界,Θ 是上下都夹住

记号最终满足的关系要表达的意思
T(n)=O(f(n))T(n) ≤ c·f(n)渐近上界
T(n)=Ω(f(n))T(n) ≥ c·f(n)渐近下界
T(n)=Θ(f(n))c₁·f(n) ≤ T(n) ≤ c₂·f(n)渐近紧确界;增长同阶

c、c₁、c₂ 都是固定正数,关系要求在足够大的所有 n 上成立。对 4n+9,n≥1 时有 4n≤4n+9≤13n,因此 T(n)=Θ(n)。它可以写成 O(n²),但不能写成 Θ(n²),因为那会错误要求它至少按某个固定比例的 n² 增长。

这里明确修正原书把 O/Ω/Θ 与三种输入情况一一对应的简化表述。平均复杂度写 O(n) 本身是合法的上界声明;只有同时能给出线性下界,才可加强为 Θ(n),不能见到 O 就直接替换成 Θ。

定义核对:MIT 6.006 渐近复杂度讲义,第 1 页;本文用原创不等式说明。

知识点 07:为什么可以忽略常数系数和低阶项

以 T(n)=3n²+7n+10 为例。n≥1 时,7n≤7n²、10≤10n²,所以 3n²≤T(n)≤20n²,得到 Θ(n²)。不是把真实工作删掉,而是用固定倍数夹住整个函数,保留增长级别。

对于正的固定次数多项式,最高次项最终主导。例如 109n+n² 仍为 Θ(n²),但在 n 远小于 109 的区间里,线性项可能更大。“最终主导”不能替代工程规模下的实测与成本权衡。

也不能说“只要看公式里数字最大的项”:n³ 与 10000n² 的系数大小不是阶数。一般表达式还可能含抵消、分段或振荡,不能把多项式的速记规则当作任意函数定理。指数和阶乘要分别分析,不叫“最高次多项式项”。

知识点 08—09:顺序执行相加,重复执行按实际次数累计

下面为三个阶段打点计数:先记 2 个固定动作,再执行 3n 次动作,最后执行 n² 次动作。总数为 2+3n+n²,而不是把三个阶段全部相乘。

结构工作量何时能简写
先处理 A,再处理 BA(n)+B(n)非负成本下取主导增长
外层 n 次,每轮内层固定 m 次n·m精确次数 nm 在 m=n 时等于 n²;若 m=Θ(n),增长阶也为 Θ(n²)
第 i 轮内层执行 i 次把各轮次数求和不能未经推导直接乘 n
遇到目标就提前返回取决于结束位置需指定最好、最坏或输入分布

这里省略循环控制等细节,只对选定动作精确计数;这些常数倍控制开销不会改变本例的平方增长结论。函数的代码行数不随 n 变化,但循环体执行次数会变化。

Python
def mixed_work(n):
    count = 2
    for _ in range(3 * n):
        count += 1
    for _ in range(n):
        for _ in range(n):
            count += 1
    return count

for n in (0, 1, 2, 4):
    print(n, mixed_work(n))

知识点 10:三种“两层循环”,不能只看外形

第一种计算两个集合的全部索引配对,n 行、每行 m 次,所以 n·m;第二种每一项只与后续项比较,各轮分别是 n−1、n−2、…、0,总数 n(n−1)/2;第三种内层永远只有 5 次,所以总数 5n。

同为两层循环,分别可以是 Θ(nm)、Θ(n²)、Θ(n)。三角形少了一半左右的候选对,但一半是固定倍数,不会把平方增长变成线性。n=0 或 m=0 时计数可为 0,仍有函数和外层控制开销;紧确界说的是相应规模增长的适用区域。

Python
def loop_shapes(n, m):
    rectangle = 0
    for _ in range(n):
        for _ in range(m):
            rectangle += 1
    triangle = 0
    for left in range(n):
        for right in range(left + 1, n):
            triangle += 1
    fixed_inner = 0
    for _ in range(n):
        for _ in range(5):
            fixed_inner += 1
    return rectangle, triangle, fixed_inner

for n, m in ((0, 3), (1, 3), (4, 3)):
    print(n, m, loop_shapes(n, m))

知识点 11:共享进度的嵌套,为什么可能只做 n 次内层动作

下面的 cursor 在外层开始前初始化。第一轮 while 推进到 n,此后外层各轮都发现 cursor < n 为假。内层的递增动作在整个函数内总共 n 次,不是每轮都 n 次。外层仍执行 n 次,所以整体为 Θ(n)(单位成本模型)。

关键问题是:进度会不会重置?若把 cursor=0 放进外层,每轮都会从头推进,总数变成 n²。不能因为看到 while 在 for 里面,就忽略变量的生存范围和更新轨迹。更完整的例子可复用 既有共享进度笔记。

Python
def shared_progress(n):
    cursor = 0
    advances = 0
    for _ in range(n):
        while cursor < n:
            cursor += 1
            advances += 1
    return advances

for n in (0, 1, 4, 8):
    print(n, shared_progress(n))

知识点 12—13:每次减半,为什么是对数,而不是 n/2 次

从 16 开始每轮整除 2,状态是 16→8→4→2→1,循环体执行 4 次。n/2 描述第一轮后剩余规模,不是总轮数。忽略取整时,k 轮后剩 n/2k;要到 1,令 2k≈n,所以 k≈log₂n。

下面循环条件是 current>1,n≥1 时精确轮数为 ⌊log₂n⌋;例如 15→7→3→1,只有 3 轮。另一种从 1 开始翻倍、直到不小于 n 的写法,n>1 时轮数是 ⌈log₂n⌉。两者同为 Θ(log n),但精确边界不同。

代码把每轮状态放到列表,只为观察,额外保存了对数数量的状态;若只数轮次,可以不保存列表。这里明确检查 n 的类型和非负范围,避免把负数或浮点除法行为混入定义。

Python
def halving_trace(n):
    if type(n) is not int:
        raise TypeError("n must be an integer")
    if n < 0:
        raise ValueError("n must be nonnegative")
    states = []
    current = n
    while current > 1:
        states.append(current)
        current //= 2
    return states

for n in (0, 1, 2, 3, 15, 16):
    states = halving_trace(n)
    print(n, states, len(states))

知识点 14:对数底数何时可以省略

对任何固定且大于 1的底数 a、b,logₐn=logᵦn/logᵦa。分母是与 n 无关的正数,因此这两种对数只差固定倍数,渐近阶相同。

如果底数本身随 n 改变,就不能直接当常数扔掉。例如 logₙn=1(n>1),并不等于通常的对数增长。每轮还必须真的把剩余问题缩到固定比例;仅有“分成多段”的描述不足以判定对数时间。

知识点 16:n 次任务,每次对数工作,就是 n log n

下面每个 item 都重新把 scale 设为 n,因此每个外层迭代都要完成 ⌊log₂n⌋ 次缩减。n≥1 时,打点数恰好为 n·⌊log₂n⌋,当 n≥2 增长时是 Θ(n log n)。这与前面的共享进度相反:状态被重置了。

对 n=8,外层 8 次,每次 scale 为 8、4、2 三轮,总数 24。n=1 的内层动作虽为 0,外层和调用仍需要工作;不能把计数为 0 当成零运行时间。

Python
def linear_log_work(n):
    count = 0
    for _ in range(n):
        scale = n
        while scale > 1:
            count += 1
            scale //= 2
    return count

for n in (0, 1, 2, 4, 8):
    print(n, linear_log_work(n))

知识点 17:递归要数整棵树,不只看深度

下面构造一个计数实验:规模 n 被分成 ⌊n/2⌋ 和剩余部分,两个子问题都执行;当前层还做 n 次打点。每个规模为 1 的叶子记 1 个单位,空输入记 0。

n=8 时,各层的规模和分别是 8;4+4=8;2+2+2+2=8;八个 1 又是 8。共 4 层,所以打点总数 32。对 n=2k,精确总数为 n(k+1),递归整体 Θ(n log n)。非二次幂时叶子深度可能不同,不能逐层机械照抄相同数值,但平衡划分仍保持同阶。

这只是合并类分治的工作模型,不是排序实现。真实代码中的 for 循环确实逐次打点,返回的是计数;不能把递推式算出的数值大小,直接当作计算该数值所需的时间。递归树与深度的既有讲解可进一步对照。

Python
def split_work(n):
    if n <= 1:
        return n
    left_size = n // 2
    count = split_work(left_size) + split_work(n - left_size)
    for _ in range(n):
        count += 1
    return count

for n in (0, 1, 2, 4, 8):
    print(n, split_work(n))

知识点 18:每层分两支,怎样得到指数增长

若规模每次只减少 1,而每个非基本调用都继续发起两次调用,深度约 n,总调用数便可能是 1+2+4+…+2n=2n+1−1。关键不是“代码有两次函数调用”,而是这些分支确实重复展开,且每层只减少固定规模。

下面故意不缓存相同深度的结果,用两个真实子调用返回叶子数和总调用数。depth=3 时,叶子 8 个、调用 15 次、最长链却只有 4 次同时尚未返回的调用;时间的累计工作与峰值栈深度要分开。规模每次减半的分治树,则可能只有线性数量节点,不能套同一个指数结论。

本节只运行 depth≤4 的展示和小范围验证,不运行大规模穷举。返回值等于 2n本身不是复杂度证明;实际调用展开才是依据。记忆化会改变重复展开方式,需要重新分析,见递归树与记忆化。

Python
def branch_counts(depth):
    if type(depth) is not int:
        raise TypeError("depth must be an integer")
    if depth < 0:
        raise ValueError("depth must be nonnegative")
    if depth == 0:
        return 1, 1
    left_leaves, left_calls = branch_counts(depth - 1)
    right_leaves, right_calls = branch_counts(depth - 1)
    return left_leaves + right_leaves, 1 + left_calls + right_calls

for depth in range(5):
    print(depth, branch_counts(depth))

知识点 19:n! 个方案,与完整生成所有方案的成本

n 个互不相同元素的排列数为 n!:第一个位置有 n 种选择,下一位置 n−1 种,一直到 1。空集合也有一个空排列,0!=1。下面用 itertools.permutations 生成排列,再为每个排列显式创建长度 n 的列表。

如果只数搜索树的叶子,就是 n! 个;如果真的构造并保存每一条完整排列,输出本身包含 n·n! 个元素位置,因此至少需要 Ω(n·n!) 的输出写入工作。本例完整物化采用 Θ(n·n!) 时间和输出存储(n 增长时、引用与基本控制按常数成本)。教材常说的“阶乘级搜索”,不应让你忽略每个方案的复制与输出成本。

计算数学上的 factorial(n),与枚举 n! 个排列是不同任务。知道答案为 n!,不意味着计算答案也必须执行 n! 次。示例只运行 n≤4;生成器不一次保存所有结果,也不会消除最终遍历所有结果的工作。

Python
from itertools import permutations

def materialized_orders(n):
    values = list(range(n))
    return [list(order) for order in permutations(values)]

for n in range(5):
    orders = materialized_orders(n)
    output_slots = sum(len(order) for order in orders)
    print(n, len(orders), output_slots)

知识点 20:七种常见增长,怎样对照理解

增长类典型工作结构n 翻倍后主导项约变为
Θ(1) 常数固定数量基本动作不变
Θ(log n) 对数每次缩小到固定比例,只继续一个子问题增加一个固定量(固定底数)
Θ(n) 线性逐项执行固定成本动作2 倍
Θ(n log n) 线性对数n 次对数工作;平衡分治每层线性工作略多于 2 倍,比例趋近 2
Θ(n²) 平方所有索引对或两个随 n 线性增长的完整层4 倍
Θ(2ⁿ) 指数两分支展开 n 层且无共享缓存从 2ⁿ 变为 2²ⁿ,不是 2 倍
阶乘类n、n−1、…、1 层层选择增速更快;还需计每个完整输出的成本

这是主导增长形态的对照,不是任意小规模上的固定耗时排名。多项式也不只有平方:n³、n⁴ 等同样可能出现;指数底数 2 只是示例。比较实际方案时,不得跳过输入、正确性、内存和常数成本。

知识点 21—22:最好与最坏,是同一规模下选不同输入

用线性查找观察。输入是有限普通整数列表,返回第一次匹配位置与比较次数;未找到返回 None。第 k 个位置命中(从 1 开始计),就比较 k 次;完全没有目标则比较 n 次。

对长度为 n≥1 的合法输入,最好情况是第一项命中,比较一次;最坏情况是最后一项命中或不存在目标,比较 n 次。故最好成本为 Θ(1),最坏为 Θ(n)。这是先选择输入情况,再描述该成本函数的增长,不是 O/Ω 决定该看哪个输入。

下列四次都用同一个函数:目标 8 在头部、目标 6 在中间、目标 2 在末尾、目标 99 不存在。索引 0 是成功位置,不要用 if position 当作找到的判断。

Python
def find_value_steps(values, target):
    comparisons = 0
    for index, value in enumerate(values):
        comparisons += 1
        if value == target:
            return index, comparisons
    return None, comparisons

values = [8, 4, 6, 2]
for target in (8, 6, 2, 99):
    print(target, find_value_steps(values, target))
print("empty", find_value_steps([], 99))

知识点 23:平均情况不是“最好和最坏取平均”

平均成本是某个明确输入分布下的数学期望。若目标保证存在且唯一,并且出现于每个位置的概率均为 1/n,则比较次数 1、2、…、n 的概率相同,期望为 (1+2+…+n)/n=(n+1)/2,因此平均为 Θ(n)。

这里精确值是 (n+1)/2,不是 n/2;两者增长同阶,但说精确次数时不能少掉半次。若目标总在头部,期望为 1;若目标缺失概率为 p,命中时仍均匀,期望为 p·n+(1−p)·(n+1)/2。分布不同,平均成本可以不同。

下面对 n=4 的全部 24 种排列进行穷举,计算查找 0 的比较次数;这不是随机抽几个样本。计数器输出最少、最多和精确平均值。运行同一个输入七次测出的耗时中位数,并不是这里对输入分布求得的平均复杂度。

Python
from fractions import Fraction
from itertools import permutations

def comparisons_to_zero(values):
    comparisons = 0
    for value in values:
        comparisons += 1
        if value == 0:
            return comparisons
    return comparisons

costs = [comparisons_to_zero(values) for values in permutations(range(4))]
print(len(costs), min(costs), max(costs), Fraction(sum(costs), len(costs)))

知识点 24—25:只有一层循环,也可能平方;隐藏工作不能省略

下面每轮复制一个越来越长的列表前缀。stop=1 时复制 1 个元素位置,stop=2 时复制 2 个,一直到 n。复制位置总数为 n(n+1)/2。虽然源代码只有一层 for,普通 Python 列表切片的逐项引用复制仍形成平方累计工作。

这里 copied 只统计复制了多少元素位置,不测字节,也不把每个被引用对象深拷贝。前缀列表每轮可以释放或替换,累计复制量与峰值同时存活量不是同一指标。内存部分留到 2.4。

类似地,在循环体里反复 sum(values)、线性查找或创建完整副本,都要展开该操作的成本。相反,创建 range(n) 只记录范围描述,并未立即构造 n 项列表;真正遍历 range(n) 才执行 n 轮。

机制依据:Python:列表切片产生新列表、range 的范围表示。性能口径仍采用本节模型。

Python
def copied_prefix_slots(values):
    copied = 0
    for stop in range(1, len(values) + 1):
        prefix = values[:stop]
        copied += len(prefix)
    return copied

for n in (0, 1, 4, 8):
    print(n, copied_prefix_slots(list(range(n))))

知识点 26:局部很快,不代表整个流程同样快

二分定位是对数工作,不代表“定位后插入列表”也是对数工作。Python 官方 bisect 文档明确:insort 的对数搜索之后,还有线性插入,因此整体为 O(n)。要分析的是完整任务边界,而不是只挑其中最快的一步。

同理,若每次查询前重新排序,排序工作必须计入该次任务;若先排序一次后查询很多次,可以分别报告准备成本、每次查询成本和总成本。已排序输入上的查找复杂度,不能悄悄替换成任意输入的端到端复杂度。

最好/最坏/平均、期望与摊还也不是同义词。摊还分析考察一串操作的总成本如何分摊,不等于随机输入的平均;后续列表扩容章节会再展开。阅读“常数时间操作”时,要确认它指最坏、期望还是摊还成本。

依据:Python bisect · Performance Notes。

分析一段新代码:按这六问复查

  1. 任务与规模:要求什么输出?n、m 指什么?输入是否合法、有序、可重复吗?
  2. 成本口径:统计比较、访问、复制还是完整运行?哪些操作可以按固定成本处理?
  3. 执行路径:哪些循环重置,哪些共享进度?是否提前返回?递归分几支、规模如何变化?
  4. 累计公式:先后相加、重复求和;只有条件成立时才简写成乘积。
  5. 输入情况与记号:先定义最好/最坏/分布期望,再给 O、Ω 或 Θ,避免混层。
  6. 反例与证据:空输入、重复、命中位置、非二次幂和隐藏操作是否改变结论?用计数核对小输入,通式仍给数学理由。

有限样例可以发现推导错误,但不能证明所有规模的渐近关系;单次跑分也不能证明复杂度。若计数和公式冲突,先定位退出条件、取整、是否重复计算或单次动作中隐藏的工作,不先改公式凑答案。

本节复盘:10 道建议题与参考答案

  1. “T(n)=O(n²)”是否意味着恰好平方增长?给一个反例。
  2. 用固定上下界证明 4n+9=Θ(n)。
  3. 先执行 n 次循环,再执行 n² 次,为什么不是 n³?
  4. 外层 n 次,内层每次固定 5 次,复杂度是什么?
  5. 16 每轮整除 2、条件大于 1,共几轮?15 呢?
  6. 对数底数随 n 变化,还能直接省略吗?
  7. 深度 n 的两分支、每支只减 1,为什么总调用数不是 2n?
  8. 求 n! 的值与生成所有排列,是否同一个任务?
  9. 目标存在且均匀出现,线性查找的期望比较数是多少?目标总在首位呢?
  10. 只有一层 for,循环体每次复制长度递增的前缀,为什么会平方?
1—4:符号与循环

1. 不是;T(n)=n 也属于 O(n²),但不是 Θ(n²)。2. n≥1 时 4n≤4n+9≤13n。3. 两阶段顺序执行,次数相加 n+n²,取主导项得到 Θ(n²);没有让每个外层动作都重复整段平方工作。4. 总计 5n 个内层动作,单位成本下 Θ(n)。

5—8:对数、递归与输出

5. 分别为 4 和 3 轮,取整和退出条件决定精确值。6. 不能直接省略;例如 logₙn=1。7. 前面每层的所有节点都继续分裂,总数是 1+2+…+2ⁿ=2ⁿ⁺¹−1。8. 不同;完整物化 n! 条长度 n 的结果,输出本身就需要 n·n! 个位置。

9—10:分布与隐藏工作

9. (n+1)/2;总在首位时为 1,所以必须指定分布。10. 累计复制 1+2+…+n=n(n+1)/2 个位置,一行切片内部仍有工作。

这些题目是作者建议,未记作你已提问、已完成、已复盘或掌握。可先手算,再运行小输入;有新追问时优先补到相应章节。

来源、模型说明与下一节

主题依据:krahets / 《Hello 算法》2.3 时间复杂度,2026-10-16 核对,覆盖 2.3.1—2.3.5。本篇重新组织讲解,加入原创计数代码、状态表、上下界证明、输入分布、Python 实现边界与练习;对原书“O/Ω/Θ 对应三种输入情况”及平均比较次数的简化作了明确澄清。

相关改编文字采用 CC BY-NC-SA 4.0,保留署名、来源与改动说明,遵守非商业及相同方式共享;不改变本站其他内容或源码许可。渐近定义另核对 MIT 6.006 讲义;数学例子与代码均为本文编写。

Python 机制参考官方 内置类型、itertools.permutations、bisect 性能说明和列表切片。本节不通过计时测量推断增长阶;示例验证记录保存在源码中。

现已发布 2.4 空间复杂度精讲,重点区分辅助空间、输入/输出、递归栈和峰值。其余条目以全书覆盖表为准。

发布前验证环境为 CPython 3.12.14:14 段代码逐段独立运行并核对标准输出;检查 1,089 组计数参数、4,097 个减半输入、11 个分支深度与 10 个明确拒绝的非法输入;用独立答案核对 5,465 组线性查找和 588 组二分查找,再穷举 n=1…7 的 5,913 个排列验证平均比较数。另检查 n=0…7 的排列输出数量、唯一性与完整元素位置数。这些有限验证支持示例正确性,不替代正文的渐近推导。

Hello 算法Python时间复杂度渐近分析执行计数

先定义输入和成本模型,再数执行路径,最后给出有条件的增长结论。

MORE IN DEVELOPMENT2.5 复杂度分析小结:把执行次数、存活状态与工程选择连起来