图灵机(TuringMachine)有有限个状态。其中一个状态是开始状态。这些状态的一个子集是接受状态,还有一个子集是拒绝状态。接受状态子集和拒绝状态子集不相交(不能有一个状态既是接受状态,也是拒绝状态)。
有一个字符集Σ,图灵机以Σ上的字符串ω作为输入(ω∈Σ*,Σ*是一个集合,它的元素是:由0个或多个Σ上的字符组成的有限长度的字符串)。图灵机还有一个字符集Γ,是”带“(tape)字符集。Γ包含Σ中的所有字符,还必须有一个Σ中没有的字符,就是空白字符。图灵机的带(tape)上一开始默认都是空白字符。图灵机的带,是一个无限长的带子,分成一个个的单元格,每一个单元格上写一个字符(初始都是空白符)。图灵机有一个读写头,总是位于带的某一个单元格之上。读写头对当前的单元格进行读写。读写头可以顺着带子左右移动,但一次只能移动一个单元格。Γ就是图灵机可以向带子上写的字符的集合。
图灵机的动作是这样的:根据当前所处的状态和当前读到的字符,在当前单元格上写下一个字符,向左或右移动一个单元格,进入另一个状态(对于这些动作的规定,就是图灵机的转移函数δ)。一开始,将输入字符串ω(ω∈Σ*)放在带子上,把图灵机的读写头对准ω的第一个字符,并让图灵机处于开始状态。然后图灵机就开始一步一步地运行:读字符、写字符、移动读写头、进入新状态,然后再重复......直到图灵机进入某一个接受状态,这时图灵机停机并接受ω。图灵机也有可能进入一个拒绝状态而停机,这种情况下图灵机拒绝ω。除了这两种情况,还有第三种情况:那就是图灵机永远不会停机。图灵机既不进入接受状态,也不进入拒接状态,而是一直运行下去。后两种情况,合称图灵机不接受ω。
所以,对于Σ*中的一个字符串ω,这个图灵机要么接受它,要么不接受它。图灵机不接受ω,有可能是图灵机拒绝ω,也有可能图灵机不停机。一个图灵机接受的所有字符串构成的集合(是Σ*的一个子集)作为一个语言(字符串集合即为”语言“),就是这个图灵机”识别“的语言。
有一类图灵机,它们对于任何输入字符串ω都停机(要么接受,要么拒绝。两者必居其一)。这类图灵机被称为判定器。被判定器识别的语 言称作被这个判定器”判定“(这类语言称作”可判定语言“)。如果把识别一个语言作为”计算“的通用模型(这点以后说明),那么判定器就是可以明确地给出计算结果的图灵机。要么”是“,要么”不是“。
一个图灵机的“程序”,就是固化在它的状态集以及转移函数δ中的“动作”。但也可以构建一个”通用图灵机“,它的输入字符串是一个”序列化“了的图灵机M和一个字符串ω。”通用图灵机“在字符串ω上模拟图灵机M的行为,产生和M一样的结果(接受、拒绝或不停机)。一个图灵机只有有限个状态、有穷字符集Σ和带字符集Γ,以及定义域和值域都是有穷离散集合的转移函数δ,所以只要约定好了格式,一个图灵机就可以用一个字符串来描述(序列化成了一个字符串)。”通用图灵机“有点像可存储程序的冯.诺依曼计算机。
二. 计算、算法与图灵-邱奇论题
你也许会觉得:图灵机是干吗使的?答案:计算。那什么是计算?图灵机干的事情就是计算。那2×3=6图灵机能算么?如果你把"aa|aaa"作为输入字符串ω提供给某个图灵机,这个图灵机停机,并在带子上留下aaaaaa。你给了它2个a和3个a,它给了你6个a,它不就是计算了2×3=6么。
这里对”图灵机编程“不做详细介绍了。总之图灵和邱奇证明,任何有步骤的、确定的计算过程,都可以由图灵机实现。任何可执行计算的装置:算盘、珍尼纺纱机、超级计算机等等,都可以由一台图灵机模拟。也就是说:任何计算装置,都无法超过图灵机的能力。图灵机也就定义了计算和算法。
一切计算问题都可以等价于字符串接受/不接受问题,也就是语言的识别(也许是判定)问题。也就是说一切”计算“,都可由图灵机进行。
三. 图灵机不可判定问题
上面说,有些语言(字符串集合)可被一个判定器(总会停机的图灵机)判定(识别)。给一个字符串ω,这个判定器得出结论:ω是否属于这个语言。这些语言是”可判定“语言。那么是否存在不可判定语言?也就是是否存在一个语言,不存在一个判定器可以判定它?答案是”是“。设想下面的语言:
A = {(<M>,ω) |<M>是表示图灵机M的字符串,ω是一个字符串。M接受ω}
也就是,语言A中的字符串都有两部分组成:第一部分是一个图灵机M的字符串表示<M>;第二部分是一个字符串ω。且M接受ω。
假设语言A是可判定的,也就是存在一个判定器H。当M接受ω时,H接受(<M>,ω);当M不接受ω时,H拒绝(<M>,ω)。(注意H是一个判定器,它总会停机,接受或拒绝(<M>,ω))。那么我们对H稍加改造,将它的结果取一下反:当M接受ω时,H拒绝(<M>,ω);当M不接受ω时,H接受(<M>,ω)。这很容易,只要把H的接收状态和拒绝状态互换一下身份即可。
然后我们可以再对H做一个变化,它的输入字符串仅仅是一个图灵机M的序列化字符串<M>,喂给这个M的输入不再是某个字符串ω,而就是字符串<M>本身。那么H的行为就变成了:输入给它一个表示某个图灵机的字符串<M>。把<M>当做给图灵机M的输入,若M接受<M>,则H拒绝<M>;若M不接受<M>,则H接受<M>。
精彩之处到了:若把H自己的序列化字符串<H>提供给H会发生什么?当H接受<H>时,H拒绝<H>;当H不接受<H>时,H接受<H>。理发师悖论。唯一的结论只能是,H不可能存在。就是说对于语言A,不可能构造一个判定器去判定它。A是不可判定语言。不可判定语言是存在的。
再换一种方式说一遍(不带任何符号,争取用话说明白):假如存在一个判定器(还记得判定器么?就是总会停机的图灵机,要么接受,要么拒绝,反正不会不停机),以一个图灵机的描述(也是一个字符串)和一个字符串作为输入。它可以判定这个图灵机是否接受这个字符串,也就是:这个图灵机接受这个字符串,我这个判定器就停机接受;这个图灵机不接受这个字符串,我这个判定器就停机拒绝。(注意我这个既然是判定器,它就能做到上边说的)。那么以这个判定器为基础,就可以构造另一个判定器,这个判定器只拿一个图灵机的描述(一个字符串)当输入,然后把这个描述本身当做给这个图灵机的输入,判断这个图灵机是否接受它自己的描述:它接受,我判定器就停机并接受;它不接受,我判定器就停机并拒绝。然后以这个判定器为基础,再改造一下。这次改造简单,只是把接受和拒绝反一下:判定器的输入是一个图灵机的描述(一个字符串),然后判定这个图灵机是否接受它自己的描述:它接受,我判定器就停机且接受;它不接受,我判定器就停机且拒绝。没问题吧。好!现在把我这个判定器自己的描述输入给它,它将如何?它将在自己接受自己的描述时拒绝自己的描述;在自己不接受自己的描述时接受自己的描述。悖论了!
如果存在不可判定语言,那么必然存在不可识别语言。就是无法构造一个图灵机,接受这个语言的每一个字符串(用不着拒绝每一个不属于这个语言的字符串,因为这里说的是识别,而不是判定)。因为:如果一个语言L和它的补^L都是图灵机可识别的,那么L是可判定的。证明:设识别L的图灵机是M1,识别^L的图灵机是M2。构造图灵机M,它在字符串ω上交替地一步一步地模拟M1和M2。因为一个字符串要么属于L,要么属于^L,也就是对于一个字符串ω,要么M1进入接受状态,要么M2进入接受状态。于是M总会看到M1或M2(之一)进入接受状态。如M1进入接受状态,则M停机并接受ω;若M2进入接受状态,则M停机拒绝ω。那么M就成了语言L的一个判定器。所以如果一个语言不可判定,必然它或者它的补是不可识别的。不可识别语言是存在的。
一个不可判定的语言,就是一个不可计算的问题。那是一个超出了计算机能力的问题,一个不能被任何有步骤的、确定性的算法所能解决的问题。那是什么样的问题?也许是女人心吧。