图灵机杂思(rev#2)

图灵机杂思 (rev#2)
By 刘未鹏
C++ 的罗浮宫 (http://blog.csdn.net/pongba)
C++ Template是图灵完备 turing-complete ,或者更确切的说,是图灵等价( turing-equivalent )),关于这一点是没什么悬念的,只是前几天有位朋友问到为什么说 C++ Template 是图灵完备的,为了找出当初的连接,于是又去搜了一下 wikipedia standford encyclopaedia ,谁之这一搜之下又带出了一大堆内容,于是又花了好几个时辰将图灵机的相关理论复习了一遍,顺便以四十五度角仰视了一下 Alan Turing 的生平,神奇的是在追寻链接和搜索的过程中居然翻到了一篇关于 constructive mathematics 以及一篇关于 Intuitionistic Logic 的东东,那是后话,暂且不提。先来说说 C++ Template 和图灵机。
图灵机是图灵为了研究可计算问题而构思的一个理论装置,你只要想一想有限状态机就可以大概知道图灵机是个什么概念了,只不过图灵机的内存(纸带)是潜无穷的(也就是可以任意长啦, 潜无穷 是古稀蜡人的说辞)。图灵机的定义形象的说来就像老式的电传机:一个读写头,一根纸带(可能任意长),读写头不断读取纸带上的符号,并根据内在的状态转换规则转换当前状态,同时进行一些动作,譬如插除或改写当前字符,向前 / 向后移动读写头或保持不动等。至于其抽象的定义大抵就是 有限状态机 的定义了。
图灵机的这一定义现在我们看起来似乎是很显然的,然而当时却代表着一种思想上的革命,一种从无到有。图灵机实质上抽象出了我们平素进行机械式计算的核心规律,所以才等价于 一个人 + 纸笔 + 一定的规则 进行的机械运算。
这么个理论机器首先就指明了创建计算机的可能性,然而这还不够,如果为了某一个问题就去创建一个特定的图灵机的话效率就太低了。图灵机理论的一个最美妙的结论就是存在 元图灵机 Universal Turing-Machine ,直译应为一般图灵机 / 通用图灵机,然而 元图灵机 更精确地表达了其意思),所谓元图灵机其实就是把图灵机作为运算对象的图灵机,假设有一个元图灵机 M ,一个图灵机 P 以及 P 的输入数据 D ,那么将 (P, D) 喂给元图灵机 M M 就能够吐出 P(D) (即 P D 上的结果)。而这便是现在我们所用的计算机的始祖模型,其中 M 就好比我们的计算机(元图灵机), P 则是程序(编码后的图灵机), D 则是程序 P 的输入数据。元图灵机的存在表明了我们可以用一台机器来解决所有图灵可计算( turing-computable )的问题 —— 只要喂给它解决这个特定问题的图灵机编码(程序)以及问题的输入数据即可,该元图灵机就会模拟我们喂给它的那个图灵机 P 的行为,最终给出结果。元图灵机的存在性为计算机的诞生点燃了一盏明灯,这是图灵机理论中最漂亮的发现。
关于图灵机还有两个有名的 Halting Problem Busy Beaver Problem ,不过前者更有名,但没有后者有意思,所以具体就不说了,可以 google 。这两个问题说明了图灵机并不是 万能 的,它只能解决可以 机械地 解决的问题,只不过这个 机械地 定义太过含糊,没有准确地界定,所以有必要精确定义一下图灵机到底能解决哪些问题,换句话说,到底哪些问题是图灵可计算的。
这里有一个非常漂亮的证明,是关于哪些数是图灵可计算的。我们说有无穷多个实数是图灵不可计算的。首先说明一下一个数是图灵可计算的是个什么概念,一个数是图灵可计算的就是说存在一个图灵机,给它一个空纸带,最终它能打印出任意逼近该数的结果。像 pi 、自然常数 E 以及所有多项式的根就都是图灵可计算的(可由机械步骤任意逼近),这很好理解,因为我们可以写一个程序来迭代任意逼近它们,譬如 E 就是一个无穷级数的和。但还有其它实数呢?有没有图灵不可计算的实数?要想弄明白这个问题,先要考虑一共存在多少个图灵机(废话,当然是无穷多个了,但 无穷多 也有一个级别问题 :) ),为此先将图灵机进行编码,由于图灵机的状态是有限的,将图灵机编码为一个五元组 ( Old_State, Symbol, New_State, New_Symbol, Move) 的序列(或更多 / 更少都有可能,这是比较常见的一种编码方式,但总之一定是 N-TUPLE N 有限),那么我们来考虑一个 N-state(N 个状态 ) M-symbol 的图灵机一共有多少种可能,为此,先考虑对应 N-state M-symbol 5-tuple (见上面的定义)有多少个,根据简单的排列组合规律,一共是 N*M*N*M*3 (最后一个 3 是指 Move 的可能性 —— 静止 / 向前 / 向后),换句话说,也就是以 M,N 为参数的一个 upper-bounded function OK ,现在来考虑一个图灵机的编码形式,一个图灵机其实就是由一集 5-tuple 所构成,所以既然 N-state,M-symbol 5-tuple 一共有 N*M*N*M*3 个,换句话说,这个由所有可能的 5-tuple 所构成的集合中一共有 N*M*N*M*3 个元素,那么这个集合的所有子集合的个数也就是 N-state,M-symbol 的所有图灵机的个数,根据幂集的定义,这也就是 2N*M*N*M*3 个。这里为了简单起见,我们暂且固定 M M­­0 ,即 symbol 的个数,这样所有 M0-state 的图灵机的个数就是
21*M0*1*M0*3+22*M0*2*M0*3+23*M0*3*M0*3+… =
现在我们看到,这个和式的每一项都是 countable 的,而 Z ×Z 尚且是可列集,就别说这里每项都有限的情况了,所以很容易就可以和 Z 建立起单射 (injective) ,换句话说,所有 M0-state 的图灵机的集合是一个可列集。好了,现在把 M0 换成变量,既然所有 M0 state 的图灵机集合都是可列的,而 M0 又是自然数(可列集),再加上 可列集个可列集 仍然是可列集这一性质,就容易得出一切图灵机构成的集合是可列集这一结论了。
其实证明一切图灵机所构成的集合的可列性还有一种更简单但取巧一点的办法:我们知道计算机语言是图灵等价的,所以讨论 一切图灵机 其实相当于讨论 一切计算机程序 ,而从二进制层面来看,一个计算机程序可以被看成 0 1 的序列,基于这种表示, 一切计算机程序 所成集合的势也就呼之欲出了,因为 一切计算机程序 ”=“ 所有长度为 1 的计算机程序 ”+“ 所有长度为 2 的计算机程序 ”+“ 所有长度为 3 的计算机程序 ”+… 。把 所有长度为 i 的计算机程序 所成集合记为 Si 一切计算机程序 所成集合记为 S ,立即有: ; 以及 。我们记 Si 中的第 j 个元素为 Si(j) ,那么很容易就可以建立 S Z 之间的 injective function (单射),只须令 f: Si(j)->2i3j; 显然 f 就是 injective 的。这就证明了 S 的可列性质。
那么,这一结论有什么重要意义呢?非常重要,我们知道,实数集是不可列的,其势(或称 )为阿列夫 1 (而 Z ,即整数集的势则是阿列夫 0 ),所以即便耗尽所有图灵机仍然还是会有 列不出 的实数。要想严格证明这一点也很简单,只要运用 cantor 在证明集合论时运用的经典的 对角线 手法,不妨简单描述一下:
首先,既然所有图灵机的集合是一可列集,我们就可以用 M0,M1,M2,… 来表示它们。现在假设 Mi 能计算出的实数为 Ai0.Ai1Ai2Ai3 。那么我们构造一个新的实数 B=B0.B1B2B3... ,使其满足 B1≠A11,B2≠A22,B3≠A33,…,Bn≠Ann 。这样构造出来的一个实数 B 可以保证不与任何 Ai 相等,同时这个 B 并不为任何图灵机所计算出来(因为刚才的 Ai 序列已经耗尽了所有的图灵机)。这某种程度上就表明图灵机的势为阿列夫 0 (尽管对图灵机并不能称 )。
除此之外,函数空间的所有函数也并不都是图灵可计算的,一个函数为图灵可计算其实就代表存在一个图灵机可以模拟该函数的行为。这一结论通过刚才的证明就很好解释了:因为函数空间的势为阿列夫 2 ,比实数集还要大,怎么可能由图灵机来枚举尽呢?要证明也很简单,同样是对角线法,就不说了。
那么,既然我们已经从原则上肯定了有些函数是不可计算的,换句话说,有些函数你虽然知道它是函数,但你就是无法用图灵机来将它模拟出来,用今天的话说,就是无法编程实现!这可比较令人沮丧,居然有无法编程实现的函数。那么究竟能否造出这么一个函数来让人瞧瞧怎么个不能编程实现法呢?这就是所谓的 Busy Beaver 问题。 Busy Beaver 问题就是要构造这么一个函数,它用于计算任意一个 N-state 的图灵机的最大 生产率( Productivity ,生产率的定义就是给一个图灵机一条空纸带,最终当该图灵机 halt 的时候数纸带上的 1 的个数,就是其生产率。所有 N-state 的图灵机的最大生产率就是指一切 N 状态的图灵机的生产率中的最大者。很显然,这是一个 N 的函数。记为 L(n) 。那么问题是,这个 L(n) 是否是图灵可计算的?答案是不行!用反证法:假设存在这么一个图灵机 B ,能够模仿 L(n) 的行为,那么我们将它接到一个特殊的 n-state 的图灵机 I 后面( I 的作用是在纸带上留下 n 1 ,作为 B 的输入,可以证明,这样的 I 是存在的),这样形成一个新的图灵机 IB ,这个新的图灵机的作用就是计算 L(n) ,其中 n 就是 I 的状态数,也是 I 的输出, B 的输入。我们再在后面接上一个 B ,得到 IBB 这么一个图灵机,其效果为 L(L(n)) 。那么现在考虑 IBB 自身的状态数,是 I 的状态数(即 n +2 倍的 B 的状态数,假设 B 的状态数为 b b 为有限值),那么 IBB 的状态数就是 n+2b ,那么可想而知 n+2b 状态的图灵机的最大 productivity 是肯定大于等于 L(L(n)) 的,因为已经有一个图灵机的 productivity L(L(n)) 了,它就是 IBB 。这个不等式就是 L(n+2b)>=L(L(n)) ,由此我们可以推出 n+2b>=L(n) L(i)>=L(j)=>i>=j 这一结论易证),又根据 n 的任意性,我们可以令其等于 n+11 ,于是得 n+11+2b>=L(n+11) ,而我们知道 11 状态可以实现出一个将纸带上的 1 的数目翻倍的这样一个图灵机(试试看吧 :) 很简单),于是 L(n+11)>=2n (只要把前面的 n 状态用于一个产生 n 1 的图灵机,后面的 11 状态做成一个翻倍 1 的数目的图灵机即可),于是得到 n+11+2b>=2n ,于是得到 11+2b>=n 。根据 n 的任意性, b 的有限性,这就得到了矛盾!(其实这里还隐含着另一层意思,即要实现这么一个图灵机,必须要有无穷多个状态才行,即 b 要任意大,而根据图灵机的定义, b 是有限的)。
说到这里,想起还有一个经典的函数也是图灵不可计算的。上面已经提到,所有图灵可计算的实数所构成的集合是一可列集,记为 S={s|s 为图灵可计算的 } (这是因为所有图灵机构成的集合是可列集),现构造这样一个函数 f ,使得 f(j,i)=Sj(i) ;其中 Sj 就是 S 中的以 j 为下标的元素 ,Sj(i) 则是指 Sj 的第 i 位的值。我们断言这样一个 f 就是图灵不可计算的。欲证明这一点,我们假设存在一个图灵机 P ,满足 P(j,i)=Sj(i) 。现使用对角线法构造出一个新的不属于集合 S 的实数 r r 满足:
我们发现,只要 P 存在,那么 r 就也是图灵可计算的,而这又跟 r 不属于 S 是矛盾的,所以推出 “P 不存在 这一结论。(注:为什么只要 P 存在 r 就也是图灵可计算的呢?这样想,我们构造一个新的图灵机 Q ,使满足:
这么个新的图灵机 Q ,只要喂给它一个下标 i ,就会吐出 r 的第 i 位,于是 r 就成了图灵可计算的了。
Church-Turing Thesis 大意就是说,在所有以自然数为定义域的函数中,只有那些递归函数(这里递归函数也包括有限步骤的函数)才是图灵可计算的。这一结论界定了图灵机的计算能力。乃是非常重要的。其实想想也很直观,一个无穷不循环的函数自然需要无穷多个状态才能计算了。而一个图灵机的状态是有限的,在这个有限空间中转来转去最终肯定是坠入循环,这就好像两整数相除一样,要么除尽,要么无限循环小数,用鸽笼原理可以很容易地证明。
来简单说说 C++ Template 的图灵完备性( turing-complete ,更准确地说是 turing-equivalent ,因为 turing-complete 一般是指一个问题是 turing-computable 的)。要想证明一门语言的图灵完备性从原则上来说就是要证明它可以解决图灵可计算的所有问题。或者构造出一种转换途径,能够把任何图灵可计算问题的解决方法转换为这门语言的程序。但这里还有一个更取巧的方法,用这门语言实现一个 Universal Turing Machine (其实差不多就是有限状态机啦)。 C++ Template 就可以做到这一点,实际上这早已有人做过了。另外,一般来说一门语言只要有 if 判断,递归或循环结构以及最基本的赋值能力和四则运算就是图灵完备的了,而 C++ Template 恰巧这些都具备,其中 if 判断和递归结构是借助模板偏特化能力实现的,估计语言的设计者当初也没有料到这一点会给现代 C++ 带来这么大的影响 :)
另外,严格来说,现今任何计算机其实都不是 turing-equivalent 的,因为它们的内存是有穷的。而图灵机的内存则是潜无穷的。只不过只要不出现内存不耗尽的情况都一样啦 :)
前面提到的 constructive mathematics intuitive logic 不属于图灵机的内容,有空再写吧。
P.S 图灵机揭示了人类机械演算的深层机理,歌德尔的著名的不完备性定理跟图灵机的停机问题就是等价的。用通俗的话来说其实就是 没有全知全能的上帝 ,这里上帝其实就隐喻在图灵机算模型限制之下的公理体系。不过这又是另一个故事了,有时间再写吧 :)

你可能感兴趣的:(数据结构,编程,F#,J#,D语言)