《论可计算数及其在判定上的应用》简单理解

刚刚拜读了一本书, 《图灵的秘密》. 该书介绍了图灵的论文《论可计算数及其在判定上的应用》, 其指出: 一个拥有铅笔, 纸和一串明确指令的人类计算者, 可以被看做是一种图灵机. 那么图灵机是什么呢? 是图灵为了描述可计算数而引出的一个虚构的可以做一些简单操作的计算机器. 尽管这个机器很简单, 但图灵断言它再功能上等价与一个进行数学运算的人.

先提个小醒, 文章有些长, 而且还比较枯燥.

当然了, 那些数学证明并不是我关心的, 我关心的是这个图灵机. 图灵为了说明他的数学理论, 描述了一台机器, 而这台机器, 看过之后发现其实已经算是现代计算机的雏形了, 虽然他的出发点和发明计算机并不沾边吧. 先简单介绍一下这台机器.

这是一个配有一条纸带的机器, 纸带由一个个方格隔开, 图灵只使用其中相见的格子打印数据, 暂且称之为数字格, 数字格之间的用来辅助计算. 大概就这么一个纸带:

image-20201206153828061

而这台机器对操作的定义由一张状态表来决定:

状态符号操作状态切换

其中每个状态(原文为: 格局)对应的符号不同, 会执行不同 的操作并切换的对应的下一个状态. 可以将状态类比成不同的函数, 再不同的选择分支下执行不同的逻辑并调用不同的函数. 下面给出一些符合和操作的定义:

符号

  • 0/1 : 指定字符
  • Any: 非空的任意符号
  • None: 空
  • 留空: Any 和 None
  • else: 状态的其他符号都没有匹配

操作

  • R: 向右移动一格
  • L: 想左移动一格
  • P: 打印字符(P0, 打印0)
  • E: 擦除当前格子内容

OK, 对图灵这个简单的机器介绍完毕, 是不是特别简单. 一起跟着图灵来看看, 他在这台机器上都能够做些什么操作吧.

打印序列010101…

先给出一格简单的例子, 来看看这台机器是如何运行的. 打印序列01010101..., 在前面加一个小数点, 这就是二进制的1/3了, 而将0和1的位置互换之后, 就是2/3了. 来看看图灵是如何实现这一功能的.

状态符号操作状态切换
bNoneP0,Rc
cNoneRe
eNoneP1,Rf
fNoneRb

对了, 图灵的机器运行都是从状态b开始的. 让我们一起跟着状态表走起来. (图灵只使用相间的各自来打印数字, 数字格中间的用来辅助计算, 数字格不可更改)

1.展示纸带的初始状态

其中红色方块标记机器当前的扫描方格.

image-20201206155849929

2.当前状态: b, 打印0并向右移动一格, 切换状态: c

image-20201206155953039

3.当前状态c, 向右移动一格, 切换状态: e

image-20201206160044268

4.当前状态e, 打印0并向右移动一格, 切换状态: f

image-20201206160142677

5.当前状态 f, 向右移动一格, 切换回状态: b

image-20201206160224539

此时, 切换回了初始的状态, 然后周而复始的打印下去. 当然, 上述状态是可以进行简化的, 通过当前方格的不同符号, 可以进行不同的操作, 简化后的状态表:

状态符号操作状态切换
bNoneP0b
b0R,R,P1b
b1R,R,P0b

简单试一下, 简化后的状态实现的功能完全一样.

当然, 这个例子实在太简单了, 不过为了理解图灵这台机器, 还是有必要介绍一下的.

打印序列 001011011101111…

在这个序列中, 1的数量会依次加一, 也就是说要让这台机器再这个一维的纸带上记住前面打印了多少个1. 那么图灵是如何做到这一点的呢?

终于, 前面没有用到的非数字格出场了, 它用来辅助打印.

状态符号操作状态切换
bPa,R,Pa,R,P0,R,R,P0,L,Le
e1R,Px,L,L,Le
e0q
qAnyR,Rq
qNoneP1, Lp
pxE,Rq
paRf
pNoneL,Lp
fAnyR,Rf
fNoneP0,L,Le

老规矩, 直接来走一遍状态表, 边走边分析.

1. 当前状态 b, 操作Pa,R,Pa,R,P0,R,R,P0,L,L, 切换状态e

image-20201206161555876

可以发现, 状态b在初次执行之后, 就再也没有出现过了, 所以可以将其形容为初始化的操作.

2.当前状态e, 符号0,直接切换状态: q

3.当前状态q, 符号0, 操作: R,R, 切换状态q

image-20201206162121027

4.当前状态q, 符号0, 操作: R,R, 切换状态: q

image-20201206162238612

5.当前状态 q, 符号None, 操作: P1, L, 切换状态p

image-20201206162347231

可以看到, q状态的操作就是向当前序列的尾部添加一个1.

6.当前状态 p, 符号None, 操作: L,L, 切换状态p

这不的操作就是向左移动到第一个不为空的非数字格. 省略一步, 结果:

image-20201206162632707

7.当前状态p, 符号a, 操作: R, 切换状态: f

从这里可以看出来, p状态其实是充当各个状态之间的调度员角色的.

image-20201206162756151

8.当前状态f, 符号0, 操作: R,R, 切换到状态f

这里, 可以观察到, 状态f的作用是向数字的结尾打印0, 然后左移两格, 切换到e状态. 跳过中间步骤:

image-20201206163029975

**9.当前状态e, 符号1, 操作: R,Px,L,L,L, 切换状态e. **

简单分析, e状态的操作是在连续1的每一个右边打印x, 之道遇到0, 切换到q状态

image-20201206163238781

10.当前状态q, 符号0, 则向尾部添加一个1, 切换状态p

image-20201206163428097

**11.当前状态p, 符号None **

通过前面分析, p状态向左找到第一个不为空的非数字格, 在这里则是x. 然后会擦除x并切换到q状态, 既向右打印1. q状态执行完毕后的纸带状态如下:

image-20201206163718013

此时会再次切换回p状态, 向左找到a, 然后切换到f状态, 向右打印一个0.

完美, 此时其实已经发现了, 图灵的方法是在连续1的后面添加x标记, 每个x标记都对应一格末尾的1. 以此来获得上一次打印1的数量.

至此, 这台简单的机器已经能够记忆一些内容了.

数字递增

至此, 图灵这台机器虽然已经能够打印一些复杂的内容了, 不过都是一些简单的重复工作, 还没有能够称之为计算的东西. 为了引出后面的计算, 先来实现一个简单的数字递增.

这是一个不符合图灵约定的机器, 加在这里只是为了引出计算. 而且他的最高位数字向左打印. 来看一下这个状态表:

状态符号操作切换状态
bNoneP0i
i0P1r
i1P0,Li
iNoneP1r
rNoneLi
rAnyRr

这个简单的例子就不再展开步骤了, 可以动手试一下, 它确实实现了数字的递增. 从这里能够看出使用二进制的好处, 如果使用十进制相同的功能, 则i状态需要列出0-9始终状态十种状态, 而二进制则只需要两种.

计算2\sqrt{2}2

好了, 论文中首个可以称为计算的例子来了. 2\sqrt{2}2是一个无限不循环小数.

先来介绍一下在计算2\sqrt{2}2时涉及的数学知识. 首先, 2\sqrt{2}2一定是介于1-2之间的一个小数. 二进制的2\sqrt{2}2前十位是: 1.011. 如何确定下一位是0还是1呢? 方法听上去很简单, 先假设下一位是1, 然后让这个 n 位数与自身相乘, 若结果是2n-1位, 则说明结果小于2, 下一位是1. 若结果是2n位, 则大于2, 下一位是0. 这个很好证明, 可以自己动手算一下.

而一个 n 位数与自身相乘, 需要将其中的每一位与每一位相乘, 共需要计算n*n次. 产生n个局部乘积, 然后将局部乘积进行相加得到结果.

而图灵在计算时, 使用了稍有不同的方法进行乘法计算, 在运算中维护一个过程和, 每一位的相乘结果加到这个过程和中. 当然, 每一个位与位的乘积, 并不是加到过程和的最低位, 而是加到中间的某个位置上.

二进制的乘法很简单, 1*1=1, 其他情况都是0. 而乘积加到过程和的哪一位, 如果右起第2位(从0开始)乘以第3位, 则加到结果和的第2+3=5位上. 先说明, 在下方的过程和计算中, 过程和的最低位在左侧, 与数字格的顺序相反, 应该是为了简化计算过程吧.

来一起看看图灵是如何实现这个功能的呢? 这次就不直接上状态表了, 这个表有点长, 就拆成多个放到分析中了. 如果有感兴趣的, 将下方所有状态合起来就是完整的状态表了.

状态符号操作切换状态
beginNonePa,R,P1new

为了方便介绍, 我们就不从头跑一遍了, 太费篇章. 我们就从中间位置开始来看一下他的计算过程, 具体过程可以自己跟着状态走一遍. 假设现在已经计算出了三位1.01.

其中?标识当前需要计算的下一位, 并没有实际出现在纸带上. 每次计算新的一位, 都会调用new状态将扫描格重置到最左边的数字上:

状态符号操作切换状态
newaRmark_digits
newelseLnew

假设此时, 纸带的状态:

image-20201206181041943

现在对各个数字位进行标记.

状态符号操作切换状态
mark_digits0R,Px,Rmark_digits
mark_digits1R,Px,Rmark_digits
mark_digitsNoneR,Pz,R,R,Prfind_x

很简单, 在所有已知位后面都标记x. 未知位标记z, 然后在过程和的最低位打印r.

image-20201206181217503

当前状态: find_x

状态符号操作切换状态
find_xxEfirst_r
find_xafind_digits
find_xelseL,Lfind_x
first_rrR,Rlast_r
first_relseR,Rfirst_r
last_rrR,Rlast_r
last_rNonePr,R,R,Prfind_x

r是过程和的最低位, 可以将其看做0. 接下来的步骤, 会将每一个x都对应的打印两个r. 也就是说, 现在数字一共4位(包括?, 其为1). 然后打印了7个 r (2n-1). 根据之前的推测, 若结果需要新的一位, 则值为0, 否则为1.

image-20201206183028343

当前状态: find_digits

状态符号操作切换状态
find_digitsaR,Rfind_first_digit
find_digitselseL,Lfind_digits
find_first_digitxLfound_first_digit
find_first_digityLfound_first_digit
find_first_digitzLfound_second_digit
find_first_digitNoneR,Rfind_first_digit

如果未知位是1, 那么过程和7位就够用了, 否则未知位就是0. 现在, 已经有了都是0的7位过程和, 可以开始做乘法运算了.

现在, 开始准备首次的位与位相乘了. 为了记录当前是那两位在做乘法运算, 使用x, y, z进行标记. 其中x标记与y标记做乘法运算, 若自身相乘, 则标记为z. 先通过find_digits 回到第一个非数字格. 然后通过find_first_digit 跳到第一个做乘法运算的数字格. 并根据标记的不同调用不同的方法.

image-20201206183908118

当前状态: found_second_digit

状态符号操作切换状态
find_second_digitxLfound_second_digit
find_second_digityLfound_second_digit
find_second_digitNoneR,Rfind_second_digit
found_first_digit0Radd_zero
found_first_digit1R,R,Rfind_second_digit
found_second_digit0Radd_zero
found_second_digit1Radd_one
found_second_digitNoneRadd_one

这里可以看到, 若找到的数字是0, 则直接加0, 因为相乘后的结果必是0. 若相乘之后的结果是1, 则向过程和加1.

若找到的第一个数字是1, 则转换去寻找第二个数字.

image-20201206191441486

当前状态: add_one

状态符号操作切换状态
add_zerorPsadd_finished
add_zerouPvadd_finished
add_zeroelseR,Radd_zero
add_onerPvadd_finished
add_oneuPs, R,Rcarry
add_oneelseR,Radd_one

虽然给过程和中加0并不会对其值造成改变, 但是不管想其中加入了什么, 机器都需要对其进行一些维护. 之前说过, 过程和的r表示0, 其实s, t也表示0, 对应的, u, v, w 则表示1. 为什么需要多个字母来表示同一个数字呢? 是为了下一次加法运算时, 用来标识当前数字已经参与过运算, 应该将结果加到下一位上.

add_zero会将它找到的第一个r标记为s, 或者找到的第一个u标记为v. 然后结束加法操作.

而向过程和中加一, 则需要更多的操作, 毕竟数字已经变了嘛, 而且还需要处理进位. 将找到的第一个r变成v(0变成1), 或者找到的第一个u变成s(1变成0)并同时处理进位.

image-20201206192603902

当前状态: add_finished

状态符号操作切换状态
add_finishedaR,Rerase_old_x
add_finishedelseL, Ladd_finished
erase_old_xxE,L,Lprint_new_x
erase_old_xzPy,L,Lprint_new_x
erase_old_xelseR,Rerase_old_x
print_new_xaR,Rerase_old_y
print_new_xyPzfind_digits
print_new_xNonePxfind_digits
erase_old_yyE,L,Lprint_new_y
erase_old_yelseR,Rerase_old_y
print_new_yaRnew_digit_is_one
print_new_yelsePy,Rreset_new_x

此时, 加法结束了, 需要移动x, y, z标记, 来标识下一对需要相乘的数字. 简单设想一下, 若只有一个z则将其标记为y并将左侧标记为x即可. 若一个x一个y, 则将x左移一位, 但是当x到达最高位时, 需要将x重置到最低位, 同时左移y. 当y到达最左侧的时候, 计算结束.

image-20201206193501669

当前状态: find_digits

现在, 计算又回到了最初的状态, 可以开始进行新一轮的计算了. 这次相乘的结果1*1=1, 再次向过程和中加一. 结果:

image-20201206194001393

继续执行后, 需要下面几个状态(下面为每次回到find_digits状态的纸带情况)

状态符号操作切换状态
reset_new_xNoneR,Pxflag_result_digits
reset_new_xelseR,Rreset_new_x
flag_result_digitssPt,R,Runflag_result_digits
flag_result_digitsvPw,R,Runflag_result_digits
flag_result_digitselseR,Rflag_result_digits
unflag_result_digitssPr,R,Runflag_result_digits
unflag_result_digitsvPu,R,Runflag_result_digits
unflag_result_digitselsefind_digits

image-20201206194354998

image-20201206203909733

可以看到, 当x重置的时候, 下一次位与位相乘的结果需要加到过程和的第二位上, 因此, 需要对过程和的内容做少许修改: 第一个sv变成tw, 剩下的sv变成ru. 为了下一次计算的时候, 能够将结果加到对应的位置上, 就是下一次相乘结果的相加位要向后一格, 在做加一操作的时候, 只识别r, u, 所以之后的标识符还需要重置.

操作已经简单的走了, 是时候将所有状态放出来了.

状态符号操作切换状态
carryrPuadd_finished
carryuPr,R,Rcarry
carryNonePunew_digit_is_zero
new_digit_is_zeroaRprint_zero_digit
new_digit_is_zeroelseLnew_digit_is_zero
print_zero_digit0R,E,Rprint_zero_digit
print_zero_digit1R,E,Rprint_zero_digit
print_zero_digitNoneP0,R,R,Rcleanup
new_digit_is_oneaRprint_one_digit
new_digit_is_oneelseLnew_digit_is_one
print_one_digit0R,E,Rprint_one_digit
print_one_digit1R,E,Rprint_one_digit
print_one_digitNoneP1, R,R,Rcleanup
cleanupNonenew
cleanupelseE,R,Rcleanup

其中carry的操作就是进位操作, 当遇到符号1时, 将其改为0继续进位, 当遇到符号0的时候, 则改为1, 结束. 若遇到空内容, 说明计算产生第8位了, 则未知位必为0, 直接结束计算.

从上面看到, 还有一种结束情况就是y走到头了, 这时没有产生进位, 说明未知位为1.

剩余的几个状态就一带而过了, 未知位是1, 未知位是0, 以及cleanup清理所有过程和的内容.

整个乘法会以两种情况结束:

  1. 当进位产生新的位数时, 结束. 未知位是0
  2. y标记下一步走到头了, 结束. 未知位是1

至此, 已经完成了计算2\sqrt{2}2的操作. 这个状态可以周而复始的一直计算下去. 不再继续延时了, 感兴趣的可以自己按照上面的状态表算一遍. 看了上面命名的状态, 有没有觉得和函数很像呀.

其实其原理并不复杂, 就是进行0和1的简单尝试, 然后根据结果的大小来决定后一位是什么内容. 但我还是被图灵能够在一维的纸带上实现的操作折服了.

方法集

看了上面的内容, 有没有觉得少了点什么? 那一个个的不就是函数嘛.而图灵下一步要做的, 就是要组建一格常用表集, 作为基础来搭建一些更为复杂的表. 更形象的说法就是封装函数. 同时, 函数的封装也方便他后面构建更大的程序. 关于函数的概念就不在赘述了, 天天用的也不少了. 就给出图灵的表达形式, 大家自然能够看懂.

回看一下上面的new_digit_is_zeronew_digit_is_one两个函数, 函数化之后的标识:

状态符号操作下一个状态
print_digit(A)0R,E,Rprint_digit(A)
print_digit(A)1R,E,Rprint_digit(A)
print_digit(A)NoneP(A),R,R,Rcleanup

很好理解哈, 就不解释了. 同时, 其参数也可以传递状态. 将这个基础表称之为骨架表. 可以看的出来, 所有的骨架表都可以转换成不带参数的形式. 到这里, 其实和现在的函数式编程思想已经很接近了有木有.

举个栗子:

状态符号操作下一个状态
f(S, B, a)αLf1(S, B, a)
elseLf(S, B, a)
f1(S, B, a)aS
NoneRf2(S, B, a)
elseRf1(S, B, a)
f2(S, B, a)aS
NoneRB
elseRf1(S, B, a)

其中 α 用来标识开始.

来看一下这个骨架表是做什么用的? 简单分析一下:

  • f 函数: 向左找到标识符α, 然后转到 f1函数

    • 将扫描格进行定位
  • f1函数: 向右找, 若找到 a, 执行 S 函数, 空格向右转到 f2函数, 否则继续向右寻找

    • 找到向右的第一个 a, 执行 S 函数
  • f2函数: 向右找, 若找到 a, 执行 S 俺叔, 空格向右执行 B 函数, 否则向右转到 f1函数

    • 找到向右的第一个 a, 执行 S 函数
    • 若找到连续两个空格, 执行 B 函数(与 f1函数配合, 识别连续的两个空格)

可以看出, f 就是 find, 他会寻找 a(也是参数), 若找到, 执行 S, 没找到则执行 B.

再来看一个栗子:

状态符号操作下一个状态
e1(S)ES
e(S, B, a)f(e1(S), B, a)

看这个骨架表. 函数 e1 的作用是将符号擦除, 然后转到 S 状态.

那么相对的, e 函数的作用是, 向右找到第一个 a, 若找到了则擦除并转到 S, 若没有找到, 转到 B. 同时, 图灵还允许一个函数同时接收不同个数的参数(想到了什么? 函数重载)

状态符号操作下一个状态
e(B, a)e(e(B, a), B, a)

这个两个参数的e函数是不是有点皮了. 来分析, 三参数的e, 作用是找到 a 符号并擦除, 然后转到 S. 再看两参数的e函数, S 是什么? 还是他自己, 继续找一个 a 符号并擦除, 直到擦除调所有的 a.

也就是说, 这个函数实现的功能是, 擦除所有的 a, 然后转到 B. 从这可以看出, 图灵的思想对现在的编程提供了一定思路, 已经有函数的嵌套调用了, 膜拜了.

再来看一些定义的基础库, 来帮助理解图灵的这个概念.

找到出现的最后一格 a

函数 f 从左向右查找, 函数 g 从右向左找.

状态符号操作下一个状态
g(S)AnyRg(S)
NoneRg1(S)
g1(s)AnyRg(S)
NoneS
g1(S, a)as
elseLg1(S, a)
g(S, a)g(g1(S, a))

其中单参数的函数 g 和单参数的函数 g1配合, 将扫描格移到最右侧.

在结尾打印

状态符号操作下一个状态
pe(S, b)f(pe1(S, b), S, α)
pe1(S, b)AnyR,Rpe(S, b)
NonePbS

其中, 这个函数有一个假设, 就是最左侧有两个连续α, f 函数先将扫描格移动到最左侧α, 然后右移一格开始寻找, 这时当前格就是α.

在结尾打印两个字符

状态下一个状态
pe2(S, a, b)pe(pe(S, b), a)

直接先在结尾打印 a, 然后在在结尾打印 b

增强 find 函数

f 函数在找到所需字符后, 将扫描格向左或向右移动.

状态操作下一个状态
l(S)LS
r(S)RS
fl(S, B, a)f(l(S), B, a)
fr(S, B, a)f(r(S), B, a)

复制字符

找到用 a 标记的字符, 复制到结尾. 然后调用 S 函数.

状态符号下一个状态
c(S, B, a)fl(c1(S), B, a)
c1(S)βpe(S, β)

这里有一个特殊的地方, c1函数中, 符号β表示扫描到的字符. 当然也可以将不同的字符进行排列(可能只有0或1).

复制并擦除

状态下一个状态
ce(S, B, a)c(e(S, B, a), B, a)
ce(B, a)ce(ce(B, a), B, a)

其中三参数的ce, 会找到 a 标记的符号, 并复制到结尾. 然后调用e擦除 a 标记. 擦除后执行第一个参数的状态. 而在两参数的ce中, 传递过期的第一个参数是它自己. 就是说, 两参数的ce会将所有 a 标记的符号复制, 同时擦除 a 标记, 最终转到 B.

(在之前打印’001011011101111…'的例子中, 就可以使用这个函数对1进行复制)

看到这里已经发现了, 图灵机令人咋舌的效率问题. 为了执行以此复制并擦除, 函数 c 会先调用 f 遍历一遍(f 函数会先回到开头的位置)进行复制操作, 然后再调用 f 进行遍历并将其擦除. 而复制擦除下一个字符, 又要重复这些操作. 如果在第一次找到的时候, 就顺手擦除, 然后再进行复制, 岂不更好.

不过, 想必图灵在当时并没有考虑效率的问题, 或者说他并不关心效率的好坏, 毕竟连这台机器都是想象出来的. 现在, 图灵已经可以构建一格函数库了, 类似与现在的系统库, 想必能够构造更为强大的机器了.

数字化

接下来, 图灵对他的表格进行了重新定义. 他先是证明了所有状态都可以拆解成如下所示的三个状态:

状态符号操作符号标识
qiSjPSk, LqmN1
qiSjPSk, RqmN2
qiSjPSkqmN3

其中 Sk 用来表示符号. 规定:

  • S0 : 表示空格
  • S1 : 表示0
  • S2 : 表示1
  • 其他: 一些自定义符号

其中的操作是:

  • N1 : 打印并左移
  • N2 : 打印并右移
  • N3 : 打印

疑问, 改成这样真的能够表示之前的所有操作么? 举例一下 :

  • 擦除操作: 既打印空格. PS0
  • 左移操作: 既打印格子原本的符号.

而之前的一些较长的操作, 通过拆解也可以拆成这样的基本形式. 然后, 图灵定义了这么一格五元组:

qiSjSkLqm 用来直接表示上方的 N1 . 无用说也能和上面的表格对上吧. 有没有想到图灵要做什么? 这里每一个五元组, 都对应一个操作, 如果将多个五元组连起来, 并用分号隔开, 是不是就能完整的描述一个程序了.

至此, 他的状态表已经经过了一次转换, 变成了下标的形式. 接下来, 图灵要把下标去掉. 替换:

  • qi -> D 后面 i 个 A
  • Sj -> D 后面 j 个 C

如此一来, 他的机器就只剩下以下几个字符: D, A, C, L, R, N, ;. 其中N表示不移动. 图灵将这样的描述称为标准描述.

再然后, 图灵将仅存的这7个字符, 用1-7的数字来表示. 既: 1(A), 2©, 3(D), 4(L), 5®, 6(N), 7(😉. 那么, 将得到一个完全由数字组成的完成程序. 而这些数字连起来, 就是一个比较大的整数, 也就是说, 图灵用一个整数来完全表示了他的机器. 这个数字被图灵称为描述数.

也就是说, 一个描述数可以唯一确定一个程序, 而一个程序可以对应多个描述数(因为状态的顺序是可以随意更换的). 同时, 这也说明通过枚举所有的整数, 可以得到所有可能的计算序列.

其实与现在的程序有着异曲同工之妙, 现在的程序也不过是一串二进制的数字.

可编程通用机

接下来, 图灵描述了一个可编程的通用机器, 将程序1的描述数放到纸带的开头, 机器 A 通过读取并完全复刻所有格局来实现程序1的功能. 这台机器可以通过读取不同的输入纸带, 来实现不同程序的功能.

同时, 图灵证明了这样一个机器的可行性. 现在, 假设需要执行的程序是之前的交替打印0和1的程序, 其状态表如下:

状态符号操作下一个状态
bNoneP0b
0R,R,P1b
1R,R,P0b

转换成通用格局之后:

状态符号操作下一个状态
q1S0PS1,Rq2
q2S0PS2,Rq1

很简单, 打印0然后打印1, 交替进行. 将通用格局转换成可描述符号:

  • q1S0S1Rq2: DADDCRDAA
  • q2S0S2Rq1: DAADDCCRDA

输入纸带如下所示(一行显示不下, 但他还是一维纸带哦):

image-20201212171922404

每一条指令由五个连续部分组成:

  1. D 接 n 个 A: 表示状态, 最少由一个 A
  2. D 接 n 个 C: 表示识别符号
  3. D 接 n 个 C: 表示在扫描格打印的符号
  4. L/R/N: 表示扫描头的移动方向
  5. D 接 n 个 A: 表示下一个切换的状态.

接下来的证明过程, 就有些超出我的理解了, 感兴趣的朋友可以自行钻研一下, 我是看了好久, 也没搞懂.

至此, 图灵的这台机器, 其实已经有了现代计算机的雏形了. 而我, 也被这几十年前伟大的思想折服了. 牛批…

同时, 图灵的论文后面还使用这台通用机器进行了一些证明, 不过, 那并不是我所关心的内容.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508392.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP为什么empty可以访问不存在的索引

开始之前, 先抛出问题: $arr []; echo empty: , PHP_EOL; var_dump(empty($arr[1])); echo is_array: , PHP_EOL; var_dump(is_array($arr[1]));这段代码的运行结果: 你是否和我有过同样的疑问? 同样是函数, 为什么empty访问不存在的索引就不会报错呢? 按理说哈, 函数调用的…

浮点数运算丢失精度

今天碰到了这样一个情况, 使我又去翻阅了原来课本, 在Pthon中如果输入下面这段程序: print(sys.float_info.max - 1.0) print(sys.float_info.max)结果如下: 结果发现, 这数字根本没有变化. 本来这没什么, 看这数字, 10的308次方, 也就是说, 减去的1是在308位之后了, 这里没有…

变量的作用域

起因 最近闲来无事, 在 Python 官网上看到了2.0版本, 是2001年的. 打算装起来体验一下最初发布的版本, 但是发现只有 Windows 版本, 所以我就装了个 Windows10的虚拟机, 就在我打算安装的时候, 发现: 这激起了我的好胜欲, 于是我就依次安装了Windows 8, Windows 7, Windows XP…

PHP8的注解

PHP8.0增加了注解的支持, 虽然 PHP的注解没用过, 但是咱用过JAVA的注解呀. 注解这玩意怎么用? 简单说就下面几步: 定义注解类使用注解提取注解 到了PHP中, 也基本上换汤不换药. 使用 定义注解类 #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION)] cl…

HBase 数据存储结构

在HBase中, 从逻辑上来讲数据大概就长这样: 单从图中的逻辑模型来看, HBase 和 MySQL 的区别就是: 将不同的列归属与同一个列族下支持多版本数据 这看着感觉也没有那么太大的区别呀, 它解决了 MySQL 的那些问题呢? 每一个新事物的出现, 都是为了解决原本存在的问题. 对写入…

spark计算操作整理

spark 的计算流程大概如图: 其中, 通过多次处理, 生成多个中间数据, 最后对结果进行操作获得数据. 本文不涉及任何原理, 仅总结spark在处理的时候支持的所有操作, 方便后面使用的时候, 可以参照本文进行数据的处理. 以下函数整理, 基与Python中RDD对象. 数据的转换操作 数据…

软件工程模型

你在工作中, 软件的开发流程是怎样的? 你是否想过, 除了你当前使用的流程, 还存在其他怎样的流程? 现在的流程有哪些问题, 又能够如何解决? 别说, 前辈们已经给出了一些项目流程的模型, 既软件工程. 可以简单了解一下, 带动一下我这生了锈的脑子. 在很久以前, 一个软件的从…

路径.git下的文件

用了这么久的git, 可以毫不谦虚地说对git是一无所知. 每天用来用去的就是commit, add, merge 等几个有限的命令, 这不符合我这刨根问底的性格啊. 不行, 得研究研究, 从哪里下手呢? 别的咱先不说, 所有 git 项目都有这么一个文件夹.git, 不如就从它入手 ? 那咱就看看这个文件夹…

git 操作二进制文件

平常用git进行项目管理已经稀松平常了, 今天咱来点不一样的. 平常管理的都是普通的文本文件, 如果是二进制文件, git能够处理么? 比如word文档. 测试一下. 新建一个项目, 在其中创建test1.docx, test2.txt两个空文件并提交. 之后编辑文件并添加标题, git diff看一下效果: 效…

搭建个人博客

一直都想着搞一个自己的个人博客, 拖着这么久, 最近终于开始动手了. 故留下一篇完整的记录, 若你也刚好有相同的需要, 那这篇文章应该恰好能够帮助到你. 准备 云服务器个人域名 如果没有云服务器, 将应用跑在自己的电脑上, 通过内网穿透大概也能达到效果, 但是个人电脑实在没…

nginx 配置文件的匹配规则

引出 之前在对php-fpm 进行nginx代理时, 为了对后台限定 IP 访问, 添加了如下配置: location ^~ /admin {allow 127.0.0.1;deny all; }结果呢? 所有admin路径下的php文件, 全都没有解析, 变成文件下载了. 当时我不知道是什么问题, 不过将这段配置去掉之后, 问题就消失了. 所…

计算机是如何进行时间同步的

WHY 在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的…

WordPress架构简单剖析

前言 最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件…

阿里云定时任务并自动释放

前言 最近写了一个爬虫脚本, 脚本跑在一台北京的 ecs 上. 但奈何因某种未知力量, 需要连接代理才能访问目标网站. 本来想着自己搭代理, 但是太贵了, 就暂时搁置了. 直到我发现了这个: 阿里云香港的服务器, 一个小时才5分钱. 如果脚本直接跑在香港服务器上不就可以了咩, 按照这…

智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.金豺算法4.实验参数设定5.算法结果6.参考文献7.MA…

PHP-PDO参数绑定问题

前言 今天在执行这样一段代码: $data [username > hujingnb,address > beijing, ]; $dbh new PDO("mysql:host{$host};dbname{$dbname}", $username, $password); $statement $dbh->prepare(INSERT INTO test_user (username, address) VALUES (:usern…

Python 的协程

前言 最近在看部分Python源码时, 发现了async 这个关键字. 查了一下发现了Python中的协程. 协程这玩意, 在GO中我用过啊, 简单说, 就是一个轻量级的线程嘛, 由语言自己来实现不同协程的调度. 想着Python中可能也是差不多的东西吧. 但是我Google搜了一下, 前面的说明都给出了下…

虚拟内存分页机制的地址映射

概述 在之前的文章虚拟内存对分页机制做了简单的介绍. 还有一个疑问, 那就是如何将虚存中的逻辑地址映射为物理地址呢? 今天就来简单分析一下. 对于一个分页的地址来说, 一般包含两个元素: 页号: 第几页偏移量: 当前页的第几个字节 以下以 addr_virtual(p, o)表示一个逻辑…

虚拟内存分页机制的页面置换

前言 之前简单介绍过虚拟内存是如何与物理内存进行地址映射的: 虚拟内存分页机制的地址映射, 但是仅仅地址映射是不够的, 在地址映射说过会有缺页的情况, 此时就需要操作操作系统将缺少的页加载到内存中. 但是, 如果内存满了怎么办呢? 毕竟虚拟内存一般都要大于物理内存的, 不…

Kubernetes各个组件的概念

前言 Kubernetes中的概念太多了, 什么Pod Service Deployment 等等等等, 给刚接触的我都整蒙了. 通过几天观察下来, 说一下我对各个组件的理解. 此文章仅仅对这些概念做一个简单的介绍, 不至于后面看其他文章的时候一头雾水. Node Node很好理解. 就是服务实际运行的实例, 可…