Skip to content

第26章 复杂度理论

学习目标

  • 掌握渐近符号的形式化定义与运算
  • 理解摊还分析的三种方法
  • 掌握NP完全性理论的基本概念
  • 理解问题复杂度的下界证明方法
  • 掌握算法优化的理论基础

26.1 渐近符号

26.1.1 形式化定义

定义 26.1(大O符号) $f(n) = O(g(n))$ 当且仅当存在正常数 $c$ 和 $n_0$,使得:

$$\forall n \geq n_0: 0 \leq f(n) \leq c \cdot g(n)$$

定义 26.2(大Ω符号) $f(n) = \Omega(g(n))$ 当且仅当存在正常数 $c$ 和 $n_0$,使得:

$$\forall n \geq n_0: 0 \leq c \cdot g(n) \leq f(n)$$

定义 26.3(大Θ符号) $f(n) = \Theta(g(n))$ 当且仅当:

$$f(n) = O(g(n)) \land f(n) = \Omega(g(n))$$

定义 26.4(小o符号) $f(n) = o(g(n))$ 当且仅当:

$$\lim_{n \to \infty} \frac{f(n)}{g(n)} = 0$$

定义 26.5(小ω符号) $f(n) = \omega(g(n))$ 当且仅当:

$$\lim_{n \to \infty} \frac{f(n)}{g(n)} = \infty$$

26.1.2 性质

定理 26.1(传递性)

  • $f = O(g) \land g = O(h) \Rightarrow f = O(h)$
  • $f = \Omega(g) \land g = \Omega(h) \Rightarrow f = \Omega(h)$
  • $f = \Theta(g) \land g = \Theta(h) \Rightarrow f = \Theta(h)$

定理 26.2(自反性)

  • $f = O(f)$
  • $f = \Omega(f)$
  • $f = \Theta(f)$

定理 26.3(对称性) $f = \Theta(g) \Leftrightarrow g = \Theta(f)$

定理 26.4(转置对称性) $f = O(g) \Leftrightarrow g = \Omega(f)$

26.1.3 运算规则

定理 26.5(加法规则) 若 $f_1 = O(g_1)$ 且 $f_2 = O(g_2)$,则:

$$f_1 + f_2 = O(\max(g_1, g_2))$$

定理 26.6(乘法规则) 若 $f_1 = O(g_1)$ 且 $f_2 = O(g_2)$,则:

$$f_1 \cdot f_2 = O(g_1 \cdot g_2)$$

定理 26.7(多项式规则) 对于 $k$ 次多项式 $p(n)$:

$$p(n) = \Theta(n^k)$$


26.2 摊还分析

26.2.1 形式化定义

定义 26.6(摊还代价) 对于数据结构操作序列,摊还代价是总代价除以操作次数。

定义 26.7(摊还分析) 摊还分析证明数据结构操作序列的总代价上界,即使单次操作代价可能很高。

26.2.2 聚合分析

定义 26.8(聚合分析) 直接计算 $n$ 次操作的总代价 $T(n)$,则摊还代价为 $T(n)/n$。

例:栈的多重弹出

python
class Stack:
    def __init__(self):
        self.items = []
    
    def push(self, x):
        self.items.append(x)
    
    def pop(self):
        return self.items.pop() if self.items else None
    
    def multipop(self, k):
        result = []
        while self.items and k > 0:
            result.append(self.items.pop())
            k -= 1
        return result

定理 26.8 $n$ 次栈操作(push、pop、multipop)的总代价为 $O(n)$。

证明:每个元素最多被push一次、pop一次。总操作数不超过 $2n$,故总代价 $O(n)$。摊还代价 $O(1)$。 ∎

26.2.3 核算方法

定义 26.9(核算方法) 为每个操作分配摊还代价,使得摊还代价之和等于实际代价之和加上存储的"信用"。

条件:对于任意操作序列,累计信用非负。

例:动态数组扩容

python
class DynamicArray:
    def __init__(self):
        self.capacity = 1
        self.size = 0
        self.array = [None] * self.capacity
    
    def append(self, x):
        if self.size == self.capacity:
            self._resize()
        self.array[self.size] = x
        self.size += 1
    
    def _resize(self):
        new_capacity = 2 * self.capacity
        new_array = [None] * new_capacity
        for i in range(self.size):
            new_array[i] = self.array[i]
        self.array = new_array
        self.capacity = new_capacity

定理 26.9 动态数组append操作的摊还代价为 $O(1)$。

证明:设摊还代价为 3:

  • 1 用于插入元素
  • 1 存储为信用(用于未来搬迁)
  • 1 存储为信用(用于帮助搬迁其他元素)

扩容时,$n$ 个元素搬迁,使用存储的 $n$ 信用。信用始终非负。 ∎

26.2.4 势能方法

定义 26.10(势能函数) 势能函数 $\Phi$ 将数据结构状态映射到非负实数。

定义 26.11(摊还代价) 第 $i$ 次操作的摊还代价:

$$\hat{c}i = c_i + \Phi(D_i) - \Phi(D)$$

其中 $c_i$ 为实际代价,$D_i$ 为操作后状态。

定理 26.10 若 $\Phi(D_0) = 0$ 且对所有 $i$,$\Phi(D_i) \geq 0$,则:

$$\sum_{i=1}^{n} c_i \leq \sum_{i=1}^{n} \hat{c}_i$$

证明

$$\sum_{i=1}^{n} \hat{c}i = \sum^{n} (c_i + \Phi(D_i) - \Phi(D_{i-1})) = \sum_{i=1}^{n} c_i + \Phi(D_n) - \Phi(D_0)$$

由 $\Phi(D_n) \geq 0$ 和 $\Phi(D_0) = 0$:

$$\sum_{i=1}^{n} \hat{c}i \geq \sum^{n} c_i$$

例:二进制计数器

python
class BinaryCounter:
    def __init__(self, n: int):
        self.bits = [0] * n
    
    def increment(self):
        i = 0
        while i < len(self.bits) and self.bits[i] == 1:
            self.bits[i] = 0
            i += 1
        if i < len(self.bits):
            self.bits[i] = 1

定理 26.11 二进制计数器increment操作的摊还代价为 $O(1)$。

证明:定义势能 $\Phi$ 为计数器中 1 的个数。

  • 翻转 1→0:实际代价 1,势能减少 1,摊还代价 0
  • 翻转 0→1:实际代价 1,势能增加 1,摊还代价 2

每次increment最多翻转一个 0→1,故摊还代价最多 2。 ∎


26.3 NP完全性理论

26.3.1 计算模型

定义 26.12(判定问题) 判定问题的答案为"是"或"否"。

定义 26.13(确定性图灵机) 确定性图灵机(DTM)在每一步有唯一确定的转移。

定义 26.14(非确定性图灵机) 非确定性图灵机(NTM)在每一步可能有多个转移,只要存在一条接受路径即接受。

26.3.2 复杂度类

定义 26.15(P类) P类是确定性图灵机在多项式时间内可解的判定问题集合:

$$P = \bigcup_{k=1}^{\infty} \text{DTIME}(n^k)$$

定义 26.16(NP类) NP类是非确定性图灵机在多项式时间内可解的判定问题集合:

$$NP = \bigcup_{k=1}^{\infty} \text{NTIME}(n^k)$$

定义 26.17(NP等价刻画) 问题 $L \in NP$ 当且仅当存在多项式时间算法验证"是"实例。

定理 26.12 $P \subseteq NP$。

证明:确定性图灵机是非确定性图灵机的特例。 ∎

26.3.3 多项式归约

定义 26.18(多项式时间归约) 问题 $A$ 多项式时间归约到 $B$(记作 $A \leq_p B$),若存在多项式时间可计算的函数 $f$,使得:

$$x \in A \Leftrightarrow f(x) \in B$$

定理 26.13(归约传递性) 若 $A \leq_p B$ 且 $B \leq_p C$,则 $A \leq_p C$。

证明:设 $f$ 将 $A$ 归约到 $B$,$g$ 将 $B$ 归约到 $C$。则 $g \circ f$ 将 $A$ 归约到 $C$。多项式函数的复合仍为多项式。 ∎

26.3.4 NP完全性

定义 26.19(NP困难) 问题 $B$ 是NP困难的,若对所有 $A \in NP$:$A \leq_p B$。

定义 26.20(NP完全) 问题 $B$ 是NP完全的,若 $B \in NP$ 且 $B$ 是NP困难的。

定理 26.14(Cook-Levin定理) 布尔可满足性问题(SAT)是NP完全的。

证明概要

  1. SAT $\in NP$:给定赋值,可在多项式时间验证是否满足公式
  2. SAT是NP困难的:模拟NTM的计算过程,构造布尔公式表示计算历史

定理 26.15 若存在NP完全问题 $L \in P$,则 $P = NP$。

证明:设 $L$ 为NP完全且 $L \in P$。对于任意 $A \in NP$,由NP完全性,$A \leq_p L$。由 $L \in P$,$A \in P$。故 $NP \subseteq P$,结合 $P \subseteq NP$,得 $P = NP$。 ∎

26.3.5 经典NP完全问题

问题描述
SAT布尔公式可满足性
3-SAT3-CNF公式可满足性
顶点覆盖图是否存在大小 $\leq k$ 的顶点覆盖
独立集图是否存在大小 $\geq k$ 的独立集
图是否存在大小 $\geq k$ 的团
哈密顿路径图是否存在经过所有顶点的路径
TSP判定是否存在长度 $\leq k$ 的哈密顿回路
背包判定是否存在价值和 $\geq v$ 的物品子集

26.4 下界理论

26.4.1 信息论下界

定义 26.21(信息论下界) 基于问题输出空间大小的下界。

定理 26.16(排序下界) 比较排序的最坏情况需要 $\Omega(n \log n)$ 次比较。

证明:排序问题的输出空间大小为 $n!$(所有排列)。每次比较最多将输出空间减半。需要至少 $\log_2(n!) = \Omega(n \log n)$ 次比较。 ∎

26.4.2 对抗性论证

定义 26.22(对抗者) 对抗者是一个"恶意"的对手,根据算法的查询给出最不利的回答。

定理 26.17(查找下界) 在最坏情况下,查找有序数组中的元素需要 $\Omega(\log n)$ 次比较。

证明:对抗者维护可能的答案区间。每次比较后,对抗者选择保留较大的区间。区间大小最多减半,需要 $\Omega(\log n)$ 次比较。 ∎

26.4.3 代数决策树模型

定义 26.23(代数决策树) 代数决策树是每个内部节点包含多项式比较 $f(x) \geq 0$ 的决策树。

定理 26.18(Ben-Or定理) 对于判定问题 $W \subseteq \mathbb{R}^n$,若 $W$ 有 $N$ 个连通分支,则代数决策树高度为:

$$h = \Omega(\log N - n)$$


26.5 算法优化策略

26.5.1 空间换时间

原理:使用额外空间存储中间结果,减少重复计算。

例:记忆化搜索

python
from functools import lru_cache
from typing import Dict

def fibonacci_memo(n: int, memo: Dict[int, int] = None) -> int:
    if memo is None:
        memo = {}
    
    if n in memo:
        return memo[n]
    
    if n <= 1:
        return n
    
    memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
    return memo[n]

@lru_cache(maxsize=None)
def fibonacci_lru(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci_lru(n - 1) + fibonacci_lru(n - 2)

26.5.2 预处理

原理:提前计算并存储信息,加速后续查询。

例:前缀和

python
class PrefixSum:
    def __init__(self, arr: list):
        self.prefix = [0]
        for x in arr:
            self.prefix.append(self.prefix[-1] + x)
    
    def range_sum(self, left: int, right: int) -> int:
        return self.prefix[right + 1] - self.prefix[left]

26.5.3 懒惰求值

原理:延迟计算直到真正需要时。

python
class LazyRange:
    def __init__(self, start: int, end: int):
        self.start = start
        self.end = end
    
    def __iter__(self):
        return iter(range(self.start, self.end))
    
    def __getitem__(self, index: int) -> int:
        return self.start + index
    
    def __len__(self) -> int:
        return self.end - self.start

26.5.4 分支预测优化

python
def sum_with_branch_prediction(arr: list, threshold: int) -> int:
    total = 0
    for x in arr:
        if x > threshold:
            total += x
    return total

def sum_branch_free(arr: list, threshold: int) -> int:
    total = 0
    for x in arr:
        total += x * (x > threshold)
    return total

26.6 复杂度层次

26.6.1 时间层次定理

定理 26.19(时间层次定理) 对于时间可构造函数 $f(n)$:

$$\text{DTIME}(f(n)) \subsetneq \text{DTIME}(f(n)^2)$$

推论 26.1 $P \subsetneq \text{EXP}$。

26.6.2 空间层次定理

定理 26.20(空间层次定理) 对于空间可构造函数 $f(n)$:

$$\text{DSPACE}(f(n)) \subsetneq \text{DSPACE}(f(n) \log f(n))$$

26.6.3 复杂度类关系

$$\text{L} \subseteq \text{NL} \subseteq P \subseteq \text{NP} \subseteq \text{PSPACE} \subseteq \text{EXP}$$


26.7 本章小结

本章介绍了复杂度理论:

  1. 渐近符号:$O$、$\Omega$、$\Theta$、$o$、$\omega$ 的形式化定义
  2. 摊还分析:聚合、核算、势能三种方法
  3. NP完全性:P vs NP、多项式归约、NP完全问题
  4. 下界理论:信息论下界、对抗性论证
  5. 优化策略:空间换时间、预处理、懒惰求值

结语

恭喜您完成《Python数据结构》的学习!

本书从基础数据结构到高级算法,系统介绍了:

  • 线性结构:列表、栈、队列
  • 树形结构:二叉树、平衡树、堆
  • 图结构:表示、遍历、最短路径、最小生成树
  • 哈希结构:哈希表、冲突解决
  • 排序算法:比较排序、线性排序
  • 搜索算法:二分查找、高级搜索
  • 算法范式:分治、动态规划、贪心
  • 复杂度理论:渐近分析、NP完全性

数据结构与算法是计算机科学的基石。持续学习与实践将使您成为更优秀的程序员。


参考文献

  1. Cormen, T. H., et al. (2009). Introduction to Algorithms, 3rd ed. MIT Press.
  2. Sipser, M. (2012). Introduction to the Theory of Computation, 3rd ed. Cengage Learning.
  3. Arora, S., & Barak, B. (2009). Computational Complexity: A Modern Approach. Cambridge University Press.
  4. Tarjan, R. E. (1985). Amortized computational complexity. SIAM Journal on Algebraic Discrete Methods, 6(2), 306-318.
  5. Cook, S. A. (1971). The complexity of theorem-proving procedures. STOC, 151-158.

Python技术丛书 - 江苏省宿城中等专业学校