递归函数(一):开篇
递归函数(二):编写递归函数的思路和技巧
递归函数(三):归纳原理
递归函数(四):全函数与计算的可终止性
递归函数(五):递归集与递归可枚举集
递归函数(六):最多有多少个程序
递归函数(七):不动点算子
递归函数(八):偏序结构
递归函数(九):最小不动点定理
回顾
上文中我们讨论了全函数和部分函数,以及计算的可终止性。
本文我们从数论函数开始,给原始递归函数集增加一种新的运算,得到了一个更大的集合。
然后根据递归函数,我们可以定义递归集和递归可枚举集,
为以后讨论可计算性与可判定性打好基础。
数论函数
自然数集一般记为\(N=\lbrace 0,1,2,\cdots \rbrace\),那么\( n \)个自然数集的笛卡尔积记为\(N^n\),
于是,我们称集合\(N^n\)到\(N\)的部分函数为\(n\)元部分数论函数。
作为数论函数,\(2x\)是一个全函数,而\(x/2\),\(x-y\),\(\sqrt{x}\)只是部分函数,
它们的计算结果,\(3/2\),\(4-6\),\(\sqrt{5}\)都不在\(N\)中,于是相应定义域中的点可视为没有定义。
为什么讨论数论函数呢,其一是因为它是一个典型数学的问题,
另外一点,则是因为我们经常把其他数学问题转换成数论问题,例如,哥德尔编码。
本文中,使用数论函数,可以简化我们的描述方式。
一个谓词,指的是返回布尔值的函数,
我们还可以将谓词看做值域为\(\lbrace 0,1\rbrace \)的一个数论函数。
\(0\)代表True
,\(1\)代表False
。
极小化算子
在前一篇中,我们从三个初始函数出发,
通过合成运算和原始递归运算,得到了原始递归函数集,
递归函数集是相对于这两种运算封闭的。
然而,这样定义的原始递归函数,并不能包括所有的数论函数,
一个典型的例子就是,阿克曼函数,
ackermann :: Int -> Int -> Int
ackermann 0 x = x+1
ackermann k 0 = ackermann (k-1) 1
ackermann k x = ackermann (k-1) $ ackermann k x-1
它并不是一个原始递归函数,(证略
因此原始递归函数集并不足以表示计算机程序中的所有函数。
为此,我们需要对原始递归函数集进行扩充,我们定义一个新的运算,称为极小化运算,
设\(P(x_1,\cdots ,x_n,t)\)是一个谓词,令
\(f(x_1,\cdots ,x_n)=min\ P(x_1,\cdots ,x_n,t)\)
\(f(x_1,\cdots ,x_n)\)的值,或者是使\(P(x_1,\cdots ,x_n,t)\)为真的最小\(t\)值,
或者无定义,此时不存在\(t\)使得\(P(x_1,\cdots ,x_n,t)\)为真。
这样通过\(min\)得到\(f(x_1,\cdots ,x_n)\)的过程称为极小化运算,
也称部分函数\(f(x_1,\cdots ,x_n)\)是由谓词经过极小化运算得到的。
以上我们给谓词定义了极小化运算,现在我们将极小化运算推广到一般的函数上面,
设\(g(x_1,\cdots ,x_n,t)\)是一个\(n+1\)元函数,令
\(f(x_1,\cdots ,x_n)=min\lbrace g(x_1,\cdots ,x_n,t)=0\rbrace \)
则称部分函数\(f(x_1,\cdots ,x_n)\)是由函数\(g(x_1,\cdots ,x_n,t)\)经过极小化运算得到的。
递归函数集
和定义原始递归函数集一样,我们从以下三个初始函数出发,
(1)零函数\(n(x)=0\)
(2)后继函数\(s(x)=x+1\)
(3)投影函数\(u^n_i(x_1,\cdots ,x_n)=x_i\),\(i\leqslant i\leqslant n\)
由初始函数,经过有限次合成运算,原始递归运算,以及极小化运算,得到的函数称为递归函数。
递归函数并不一定是全函数,因为极小化运算可能会导致结果函数在某些点无定义,
递归的部分函数称为部分递归函数。
可以证明阿克曼函数是递归函数,但不是原始递归函数,
因此,原始递归函数集是递归函数集的真子集。
递归可枚举集
在具体实践中,我们经常会遇到这样的问题,
给定一个元素,我们需要判断这个元素是否属于某个集合。
这种问题,称为集合的成员资格问题。
沿用这一思路,我们可以使用一个谓词\(\chi _B\)来定义相应的集合\(B\subseteq N\),
\(B=\lbrace x\in N|\chi _B(x)\rbrace \)
谓词\(\chi _B(x)\)为真,则\(x\in B\)。
这个谓词\(\chi _B(x)\),通常称为集合\(B\)的特征函数。
如果特征函数\(\chi _B\)是第一个递归的全函数,
则我们总是可以判断\(\chi _B(x)\)等于\(0\)还是\(1\),
这样的集合\(B\)称为递归集。
如果存在部分递归函数\(g\),使得\(B=\lbrace x\in N|g(x)\downarrow \rbrace\),
即,\(x\in B\)当且仅当\(g\)在\(x\)处有定义,
则称集合\(B\)是一个递归可枚举集。
因此,对于每一个自然数\(x\in N\),
我们总是可以通过递归集\(B\)的特征函数\(\chi _B\),来判断\(x\)是否\(B\)的成员。
而对于递归可枚举集,就不容乐观了,
如果某个自然数\(x\in N\)是\(B\)的成员,那么我们可以断定这件事,因为\(g(x)\)有定义,
但是如果某个自然数\(y\in N\)不是\(B\)的成员,我们就不能确定,因为这时候\(g(x)\)无定义。
(\(g(x)\)无定义,则它对应的图灵机不停机,后文我们详细讨论
因此,集合\(B\)是递归的当且仅当\(B\)和\(\bar{B}\)是递归可枚举的,
其中\(\bar{B}\)为\(B\)的补集。
总结
本文介绍了数论函数,递归函数集,然后用递归函数分别定义了递归集和递归可枚举集,
可是为什么递归可枚举集是“可枚举”的呢?
是因为每一个递归可枚举集可以一一对应一个自然数,这是怎样做到的呢?
这需要我们理解总共有多少个可能的程序,以及什么是通用程序。