数学基础
递归函数与图灵机
精确刻画哪些函数可由算法计算的形式化模型。
直观机器能计算什么?
计算器能做加法和乘法;编译器能做类型检查;AI(有时)能回答问题。但是否存在没有任何算法、无论多么巧妙,都永远无法计算的函数?阿兰·图灵的回答是——有——这来自对"算法"的一个精确数学模型:图灵机,由一条纸带、一个读写头和一张有限的规则表组成。两种看似不同的形式化,递归函数(由简单构件通过复合与递归构造而成)与图灵机(一个机械的逐步过程),结果证明恰好计算出完全相同的函数类——这是丘奇-图灵论题的有力证据,即这确实就是"一切可计算的东西"。
大学原始递归函数与 -递归函数
定义: 原始递归函数
原始递归函数是 函数中最小的一类,包含零函数、后继函数 、所有投影函数,并对复合与原始递归封闭:给定 与 ,递归式 , 定义出一个新的原始递归函数 。加法、乘法、指数运算,以及所有带固定上界的"for 循环"程序都是原始递归的——而且每个原始递归函数都是全函数(对所有输入都有定义),并且对每个输入都在有界步数内停机。
要囊括所有可计算函数——包括可能不停机的那些——我们再加一个算子。**-递归(一般递归)函数加入了无界极小化**: 返回使 成立的最小 ,依次搜索 ——若不存在这样的 ,则干脆永不返回。正是这一点使 -递归函数具备不停机的能力,克莱尼定理表明它们恰好计算出与图灵机相同的(部分)函数类。
| 类 | 构造方式 | 总会停机吗? | 例子 |
|---|---|---|---|
| 原始递归 | 复合+有界递归 | 是,总是全函数 | 指数运算 |
| 一般()递归 | 原始递归+无界 | 否,可能永远循环 | 阿克曼函数 |
| 图灵可计算(部分) | 状态+纸带+转移规则 | 否,恰好等于 -递归 | 任何算法 |
不存在这样的算法 :对每个程序索引 和输入 ,它总是停机并正确输出 (即以输入 运行程序 )是否会停机。
为什么成立?
这正是无论何种杀毒软件、编译器或集成开发环境,都无法在完全一般的意义下完美检测出死循环、死代码或"这个函数总是崩溃"的数学原因——这不是当今工程技术的局限,而是一堵坚硬的数学壁垒。
证明
反证:假设存在这样的判定器 :若 (停机)则 ,若 (永远运行)则 ,且 本身总是停机并给出正确答案。
利用 ,构造一个新程序 ,对输入 :计算 ;若 ,则 进入无限循环;若 ,则 立即停机。 是由 有效构造而成的(只是 加上一条 if 语句和一个循环),所以它有某个程序索引 ,即 。
现在提出自指的问题: 是否停机?
情形1:若 停机,那么由 的正确性,。但由 的定义, 会使 在输入 上永远循环——即 不停机。矛盾。
情形2:若 不停机,那么由 的正确性,。但由 的定义, 会使 在输入 上停机——即 确实停机。矛盾。
两种情形都导致矛盾,所以 存在的假设为假。停机问题不可判定。
进阶赖斯定理
停机问题只是一个更广泛现象的一个例子。称偏可计算函数的性质 是语义的,如果它只依赖于程序 所计算的函数 本身,而不依赖于源代码;称其为非平凡的,如果某个可计算函数具有该性质而另一个不具有。
对偏可计算函数的每个非平凡语义性质 ,集合 都是不可判定的。
为什么成立?
这一个定理立刻排除了诸如"这个程序是否计算零函数""这个程序是否计算全函数""这两个程序是否等价"等无数关于程序行为的自然问题的算法——无需为每个问题单独构造对角线论证,一击即中。
证明
不失一般性,假设处处未定义的函数 (由一个对任何输入都永不停机的程序所计算)不具有性质 ——否则改用补性质 论证,它可判定当且仅当 可判定。由于 非平凡,固定某个程序 ,其函数 具有性质 。
反证:假设 可由某算法 判定(给定一个程序索引, 停机并正确报告该程序的函数是否具有性质 )。我们把停机问题归约到 ,从而与定理1矛盾。
对任意一对 ,有效地构造(通过简单的文本操作——克莱尼的 s-m-n 定理)一个新程序 ,它对任意输入 :先模拟程序 在输入 上运行;一旦该模拟停机, 就接着模拟程序 在输入 上运行并输出其结果。
考察两种情形。若 在 上停机:对 在 上的模拟会结束,于是 在所有输入上的行为都与 完全一致,即 ——它具有性质 (因为 是语义的,只依赖于所计算的函数,而 具有 )。若 在 上不停机:对 在 上的模拟永不结束,于是 在任何输入 上都永远到不了模拟 的那一步;因此 就是处处未定义的函数 ,按假设它不具有性质 。
所以: 在 上停机 具有性质 回答"是"。由于 可计算,算法"由 计算出 ,再运行 "就能判定停机问题——与定理1矛盾。故不存在这样的 : 不可判定。
大学实际应用与典型例题
编译器优化器必须判断诸如"这段代码是否可达"或"这个变量的值是否重要"之类的问题——根据赖斯定理,这些在完全一般的意义下是不可判定的,这正是真实编译器使用保守近似的原因(它们宁可保留一些确实死掉的代码,也不愿冒删除存活代码的风险)。忙碌海狸函数 ——一台 状态、会停机的图灵机在停机前所能执行的最大步数——是一个具体的不可计算函数:已知值有 ,,,,而在2024年,协作项目"忙碌海狸挑战"(由 Tristan Stérin 及合作者主导,由化名"mxdys"的贡献者给出经 Coq 验证的证明)确立了 ——这表明即便是这个看似"简单"的组合问题,也只能逐例计算,永远无法用一个通用算法解决。
例题: 展开阿克曼函数
利用规则 、当 时 、当 时 ,逐步计算 ,并解释为何阿克曼函数是全函数却不是原始递归的。
解答
由第三条规则,。首先需要 ,而由第二条规则 。
(用第二、第一条规则展开两次)。故 ,从而 。
,而 。故 。因此 。
回到最上层:;展开 ;故 。所以 。
阿克曼函数已被证明是全函数(它总会最终归约到 的情形),所以它属于一般递归函数类——但它比每一个原始递归函数增长得都快(例如 , 已经是指数塔)。由于可以证明每个原始递归函数最终都被某个固定的 所支配,没有任何原始递归函数能等于 本身——正是这种对角线式的支配论证,而非 算子,把阿克曼函数排除在原始递归之外,尽管它是全函数。
例题: 把停机问题归约到"这个程序会打印hello吗?"
证明问题"给定程序 ,以(无输入)方式运行 ,是否曾打印出字符串 `hello`?"是不可判定的,方法是直接从停机问题归约——不使用赖斯定理。
解答
反证:假设存在算法 ,能判定运行 是否曾打印出 `hello`。我们用 来判定停机问题,从而与定理1矛盾。
对任意程序 和输入 ,有效地构造一个新程序 (无需输入):模拟 在 上运行;一旦该模拟停机, 就打印 `hello` 并停止。
若 在 上停机:模拟会结束,于是 到达打印步骤并打印 `hello`。若 在 上不停机:模拟永不结束,于是 永远到不了打印步骤,也永远不打印 `hello`。
所以 在 上停机 回答"是"。由于 可计算,"构造 再运行 "就能判定停机问题——与定理1矛盾。故 不可能存在:"打印hello"问题不可判定。(这正是编译器分析的典型模式:"这行代码是否可达"具有相同的结构。)
利用 , 等于多少?
以下哪项是停机问题不可判定性对编译器设计的影响?
赖斯定理不适用于哪个性质?
在停机问题不可判定性的对角线证明中,是什么导致了矛盾?
参考文献
- Wikipedia contributors (2024). Halting problem
- Wikipedia contributors (2024). Rice's theorem
- Wikipedia contributors (2024). Ackermann function