# 表达式膨胀 · 符号计算的阿喀琉斯之踵

> 符号计算最大的软肋，是表达式会爆炸式增长——一个 n×n 符号行列式有 n! 项，中间结果能撑到几百兆字节，把内存和时间一起吃光。这一章看膨胀怎么发生、为什么它是「精确」的必然代价，以及能怎么缓解。

- 作者：David（道雾轩）
- 专栏：符号计算的艺术（https://daiw.org/manual/symbolic-computing.md）
- 最后更新：2026-10-01
- 原文：https://daiw.org/manual/symbolic-computing/expression-swell
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 表达式膨胀 · 符号计算的阿喀琉斯之踵

前面一路见识了符号计算的威力：精确、给公式、能证明。但它有一个致命软肋，强大到能让一个**看起来不大**的问题彻底卡死。这个软肋叫**表达式膨胀（expression swell）**，是每个认真用符号计算的人都会撞上的墙。

## 眼见为实：n! 的爆炸

看一个触目惊心的例子——**符号矩阵的行列式**。给一个 $n\times n$、元素全是变量的矩阵求行列式，展开后有多少项？

```python
from sympy import *

for n in [3, 4, 5]:
    M = Matrix(n, n, symbols(f'a0:{n*n}'))
    terms = len(M.det().expand().args)
    print(f'{n}x{n} 行列式展开后有 {terms} 项   (n! = {factorial(n)})')
```

```text
3x3 行列式展开后有 6 项     (n! = 6)
4x4 行列式展开后有 24 项    (n! = 24)
5x5 行列式展开后有 120 项   (n! = 120)
```

项数正好是 $n!$——**阶乘增长**。$5\times5$ 才 120 项还好，但往上：$10\times10$ 就是 $10!=3\,628\,800$ 项，$15\times15$ 是 $15!\approx1.3\times10^{12}$ 项。**一个「才 15 行」的矩阵，它的符号行列式就有上万亿项，任何计算机都存不下。** 而同样一个 $15\times15$ 的**数值**矩阵，求行列式[眨眼就完](https://daiw.org/manual/numerical-computing/gaussian-lu)。

## 更阴险的：中间结果的膨胀

上面是**最终结果**就很大。更常见、也更阴险的是**中间表达式膨胀**——最终答案可能不大，但**算的过程中**，中间表达式会先膨胀成庞然大物：

<Callout type="warn">
  **一个符号计算，输入很小、输出也可能很小，但中间某一步的表达式能撑到几百兆字节，把内存吃光、把 CPU 拖死。** 典型的重灾区：
  - **[Gröbner 基](https://daiw.org/manual/symbolic-computing/groebner)**：最坏情况**双指数**复杂度，中间多项式的系数和项数可以疯长。
  - **符号矩阵求逆 / 特征值**：大矩阵的符号逆，每个元素都是一个巨大的分式。
  - **反复[代入](https://daiw.org/manual/symbolic-computing/substitution-matching)、[积分](https://daiw.org/manual/symbolic-computing/symbolic-integration)、化简**：每一步都可能让树长大一圈，层层叠加。

  这就是为什么符号计算「说不准」：同样一行 `simplify` 或 `integrate`，对某个式子瞬间返回，对另一个结构相近的式子却跑上几小时、或直接耗尽内存。**符号计算的运行时间和内存，极难预测**——这是它与[数值计算](https://daiw.org/manual/numerical-computing)「时间可控、内存固定」最刺眼的差别。
</Callout>

## 为什么膨胀是「精确」的必然代价

膨胀不是实现得不好，而是**「精确」这个承诺的直接后果**：

<Callout type="info">
  **数值计算能「保持小」，靠的是不停地[四舍五入](https://daiw.org/manual/numerical-computing/floating-point)——每个数永远压缩成固定的 16 位，再复杂的中间量也被强行截断成一个浮点数。符号计算做不到，因为它承诺[精确](https://daiw.org/manual/symbolic-computing/exact-numbers)：它不能丢弃任何一项、不能把 $\frac{1}{3}$ 近似成 $0.333$、不能把一个 120 项的式子「抹平」——那样就不精确了。**

  于是矛盾摆在面前：**精确 ⟺ 保留全部信息 ⟺ 表达式可能无限长大。** 数值用「可控的误差」换来了「可控的规模」；符号用「爆炸的规模」换来了「零误差」。天下没有免费的午餐——**膨胀，是精确性开出的账单。**
</Callout>

## 怎么缓解

膨胀无法根除，但有一批经验手法能拖住它：

- **别急着展开**：保持[因式分解 / 未展开](https://daiw.org/manual/symbolic-computing/factor-gcd)的紧凑形式（`factor` 后的 $(x+1)^{100}$ 只占几字节，`expand` 后是 101 项）。能不 `expand` 就不 `expand`。
- **早化简、勤化简**：在中间步骤及时 `cancel`、`simplify`，别让垃圾项滚雪球。
- **选对变量顺序 / [单项序](https://daiw.org/manual/symbolic-computing/groebner)**：Gröbner 基对变量排序极敏感，换个序可能从「算不动」变「秒出」。
- **该放手就放手**：如果符号结果注定是天文数字的表达式，**问问自己是不是真的需要那个公式**——很多时候你要的只是某些点的**数值**，那就[转数值](https://daiw.org/manual/symbolic-computing/symbolic-vs-numeric)。

最后这一条最重要，它把我们引向下一章的主题。

## 小结与下一章

**表达式膨胀是符号计算的阿喀琉斯之踵**：$n\times n$ 符号行列式有 $n!$ 项（阶乘爆炸），更阴险的是**中间表达式膨胀**——输入输出都不大，中间却撑爆内存（[Gröbner](https://daiw.org/manual/symbolic-computing/groebner)、符号求逆是重灾区），导致符号计算**运行时间和内存极难预测**。根源是**精确性的必然代价**：不能丢弃任何信息，就可能无限长大。缓解靠**不急着展开、勤化简、选好序**，以及——**该转数值时就转数值**。

膨胀这一课，逼我们正视一个根本问题：**符号计算和[数值计算](https://daiw.org/manual/numerical-computing)，到底该用哪个？** 它们是对手，更是搭档。下一章给出决策框架，以及一个把两者优点合一的漂亮手法——`lambdify`。👉 [符号 vs 数值：何时用哪个](https://daiw.org/manual/symbolic-computing/symbolic-vs-numeric)。

## 参考文献

- [1] Richard Fateman.「Why Computer Algebra Systems Can't Solve Physics Problems.」—— 表达式膨胀的实战之痛。
- [2] von zur Gathen & Gerhard.《Modern Computer Algebra》—— 中间膨胀与复杂度分析。
- [3] SymPy 文档.「Performance / Simplification.」https://docs.sympy.org/latest/explanation/gotchas.html
