《論可計算數及其在判定上的應用》簡單理解
阿新 • • 發佈:2020-12-12
剛剛拜讀了一本書, 《圖靈的祕密》. 該書介紹了圖靈的論文《論可計算數及其在判定上的應用》, 其指出: `一個擁有鉛筆, 紙和一串明確指令的人類計算者, 可以被看做是一種圖靈機`. 那麼`圖靈機`是什麼呢? 是圖靈為了描述`可計算數`而引出的一個虛構的可以做一些簡單操作的計算機器. 儘管這個機器很簡單, 但圖靈斷言它再功能上等價與一個進行數學運算的人.
先提個小醒, 文章有些長, 而且還比較枯燥.
當然了, 那些數學證明並不是我關心的, 我關心的是這個`圖靈機`. 圖靈為了說明他的數學理論, 描述了一臺機器, 而這臺機器, 看過之後發現其實已經算是現代計算機的雛形了, 雖然他的出發點和發明計算機並不沾邊吧. 先簡單介紹一下這臺機器.
這是一個配有一條紙帶的機器, 紙帶由一個個方格隔開, 圖靈只使用其中相見的格子列印資料, 暫且稱之為數字格, 數字格之間的用來輔助計算. 大概就這麼一個紙帶:
![image-20201206153828061](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206153828.png)
而這臺機器對操作的定義由一張狀態表來決定:
| 狀態 | 符號 | 操作 | 狀態切換 |
| ---- | ---- | ---- | -------- |
| | | | |
其中每個狀態(原文為: 格局)對應的符號不同, 會執行不同 的操作並切換的對應的下一個狀態. 可以將狀態類比成不同的函式, 再不同的選擇分支下執行不同的邏輯並呼叫不同的函式. 下面給出一些符合和操作的定義:
**符號**
* 0/1 : 指定字元
* Any: 非空的任意符號
* None: 空
* 留空: Any 和 None
* else: 狀態的其他符號都沒有匹配
**操作**
* R: 向右移動一格
* L: 想左移動一格
* P: 列印字元(P0, 列印0)
* E: 擦除當前格子內容
OK, 對圖靈這個簡單的機器介紹完畢, 是不是特別簡單. 一起跟著圖靈來看看, 他在這臺機器上都能夠做些什麼操作吧.
## 列印序列010101...
先給出一格簡單的例子, 來看看這臺機器是如何執行的. 列印序列`01010101...`, 在前面加一個小數點, 這就是二進位制的1/3了, 而將0和1的位置互換之後, 就是2/3了. 來看看圖靈是如何實現這一功能的.
| 狀態 | 符號 | 操作 | 狀態切換 |
| ---- | ---- | ---- | -------- |
| b | None | P0,R | c |
| c | None | R | e |
| e | None | P1,R | f |
| f | None | R | b |
對了, 圖靈的機器執行都是從狀態`b`開始的. 讓我們一起跟著狀態表走起來. (圖靈只使用相間的各自來列印數字, 數字格中間的用來輔助計算, 數字格不可更改)
**1.展示紙帶的初始狀態**
其中紅色方塊標記機器當前的掃描方格.
![image-20201206155849929](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206155849.png)
**2.當前狀態: `b`, 列印0並向右移動一格, 切換狀態: `c`**
![image-20201206155953039](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206155953.png)
**3.當前狀態`c`, 向右移動一格, 切換狀態: `e`**
![image-20201206160044268](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206160044.png)
**4.當前狀態`e`, 列印0並向右移動一格, 切換狀態: `f`**
![image-20201206160142677](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206160142.png)
**5.當前狀態 `f`, 向右移動一格, 切換回狀態: `b`**
![image-20201206160224539](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206160224.png)
此時, 切換回了初始的狀態, 然後周而復始的列印下去. 當然, 上述狀態是可以進行簡化的, 通過當前方格的不同符號, 可以進行不同的操作, 簡化後的狀態表:
| 狀態 | 符號 | 操作 | 狀態切換 |
| ---- | ---- | ---- | -------- |
| b | None | P0 | b |
| b | 0 | R,R,P1 | b |
| b | 1 | R,R,P0 | b |
簡單試一下, 簡化後的狀態實現的功能完全一樣.
當然, 這個例子實在太簡單了, 不過為了理解圖靈這臺機器, 還是有必要介紹一下的.
## 列印序列 001011011101111...
在這個序列中, 1的數量會依次加一, 也就是說要讓這臺機器再這個一維的紙帶上記住前面列印了多少個1. 那麼圖靈是如何做到這一點的呢?
終於, 前面沒有用到的非數字格出場了, 它用來輔助列印.
| 狀態 | 符號 | 操作 | 狀態切換 |
| --- | --- | --- | --- |
| b | | Pa,R,Pa,R,P0,R,R,P0,L,L | e |
| e | 1 | R,Px,L,L,L | e |
| e | 0 | | q |
| q | Any | R,R | q |
| q | None | P1, L | p |
| p | x | E,R | q |
| p | a | R | f |
| p | None | L,L | p |
| f | Any | R,R | f |
| f | None | P0,L,L | e |
老規矩, 直接來走一遍狀態表, 邊走邊分析.
**1. 當前狀態 `b`, 操作`Pa,R,Pa,R,P0,R,R,P0,L,L`, 切換狀態`e`**
![image-20201206161555876](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206161555.png)
可以發現, 狀態`b`在初次執行之後, 就再也沒有出現過了, 所以可以將其形容為初始化的操作.
**2.當前狀態`e`, 符號`0`,直接切換狀態: `q`**
**3.當前狀態`q`, 符號`0`, 操作: `R,R`, 切換狀態`q`**
![image-20201206162121027](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206162121.png)
**4.當前狀態`q`, 符號`0`, 操作: `R,R`, 切換狀態: `q`**
![image-20201206162238612](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206162238.png)
**5.當前狀態 `q`, 符號`None`, 操作: `P1, L`, 切換狀態`p`**
![image-20201206162347231](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206162347.png)
可以看到, `q`狀態的操作就是向當前序列的尾部新增一個1.
**6.當前狀態 `p`, 符號`None`, 操作: `L,L`, 切換狀態`p`**
這不的操作就是向左移動到第一個不為空的非數字格. 省略一步, 結果:
![image-20201206162632707](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206162632.png)
**7.當前狀態`p`, 符號`a`, 操作: `R`, 切換狀態: `f`**
從這裡可以看出來, `p`狀態其實是充當各個狀態之間的排程員角色的.
![image-20201206162756151](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206162756.png)
**8.當前狀態`f`, 符號`0`, 操作: `R,R`, 切換到狀態`f`**
這裡, 可以觀察到, 狀態`f`的作用是向數字的結尾列印0, 然後左移兩格, 切換到`e`狀態. 跳過中間步驟:
![image-20201206163029975](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206163030.png)
**9.當前狀態`e`, 符號`1`, 操作: `R,Px,L,L,L`, 切換狀態`e`. **
簡單分析, `e`狀態的操作是在連續`1`的每一個右邊列印`x`, 之道遇到`0`, 切換到`q`狀態
![image-20201206163238781](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206163238.png)
**10.當前狀態`q`, 符號`0`, 則向尾部新增一個1, 切換狀態`p`**
![image-20201206163428097](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206163428.png)
**11.當前狀態`p`, 符號`None` **
通過前面分析, `p`狀態向左找到第一個不為空的非數字格, 在這裡則是`x`. 然後會擦除`x`並切換到`q`狀態, 既向右列印`1`. `q`狀態執行完畢後的紙帶狀態如下:
![image-20201206163718013](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206163718.png)
此時會再次切換回`p`狀態, 向左找到`a`, 然後切換到`f`狀態, 向右列印一個`0`.
完美, 此時其實已經發現了, 圖靈的方法是在連續1的後面新增`x`標記, 每個`x`標記都對應一格末尾的`1`. 以此來獲得上一次列印`1`的數量.
至此, 這臺簡單的機器已經能夠*記憶*一些內容了.
## 數字遞增
至此, 圖靈這臺機器雖然已經能夠列印一些複雜的內容了, 不過都是一些簡單的重複工作, 還沒有能夠稱之為計算的東西. 為了引出後面的計算, 先來實現一個簡單的數字遞增.
這是一個不符合圖靈約定的機器, 加在這裡只是為了引出計算. 而且他的最高位數字向左列印. 來看一下這個狀態表:
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| b | None | P0 | i |
| i | 0 | P1 | r |
| i | 1 | P0,L | i |
| i | None | P1 | r |
| r | None | L | i |
| r | Any | R | r |
這個簡單的例子就不再展開步驟了, 可以動手試一下, 它確實實現了數字的遞增. 從這裡能夠看出使用二進位制的好處, 如果使用十進位制相同的功能, 則`i`狀態需要列出`0-9`始終狀態十種狀態, 而二進位制則只需要兩種.
## 計算$\sqrt{2}$
好了, 論文中首個可以稱為計算的例子來了. $\sqrt{2}$是一個無限不迴圈小數.
先來介紹一下在計算$\sqrt{2}$時涉及的數學知識. 首先, $\sqrt{2}$一定是介於1-2之間的一個小數. 二進位制的$\sqrt{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`位上. 先說明, 在下方的過程和計算中, 過程和的最低位在左側, 與數字格的順序相反, 應該是為了簡化計算過程吧.
來一起看看圖靈是如何實現這個功能的呢? 這次就不直接上狀態表了, 這個表有點長, 就拆成多個放到分析中了. 如果有感興趣的, 將下方所有狀態合起來就是完整的狀態表了.
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| begin | None | Pa,R,P1 | new |
為了方便介紹, 我們就不從頭跑一遍了, 太費篇章. 我們就從中間位置開始來看一下他的計算過程, 具體過程可以自己跟著狀態走一遍. 假設現在已經計算出了三位`1.01`.
其中`?`標識當前需要計算的下一位, 並沒有實際出現在紙帶上. 每次計算新的一位, 都會呼叫`new`狀態將掃描格重置到最左邊的數字上:
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| new | a | R | mark_digits |
| new | else | L | new |
假設此時, 紙帶的狀態:
![image-20201206181041943](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206181041.png)
現在對各個數字位進行標記.
| 狀態 | 符號 | 操作 | 切換狀態 |
| ---- | ---- | ---- | ---- |
| mark_digits | 0 | R,Px,R | mark_digits |
| mark_digits | 1 | R,Px,R | mark_digits |
| mark_digits | None | R,Pz,R,R,Pr | find_x |
很簡單, 在所有已知位後面都標記`x`. 未知位標記`z`, 然後在過程和的最低位列印`r`.
![image-20201206181217503](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206181217.png)
**當前狀態: `find_x`**
| 狀態 | 符號 | 操作 | 切換狀態 |
| ---- | ---- | ---- | ---- |
| find_x | x | E | first_r |
| find_x | a | | find_digits |
| find_x | else | L,L | find_x |
| first_r | r | R,R | last_r |
| first_r | else | R,R | first_r |
| last_r | r | R,R | last_r |
| last_r | None | Pr,R,R,Pr | find_x |
`r`是過程和的最低位, 可以將其看做0. 接下來的步驟, 會將每一個`x`都對應的列印兩個`r`. 也就是說, 現在數字一共4位(包括?, 其為1). 然後列印了7個 r (2n-1). 根據之前的推測, 若結果需要新的一位, 則值為0, 否則為1.
![image-20201206183028343](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206183028.png)
**當前狀態: `find_digits`**
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| find_digits | a | R,R | find_first_digit |
| find_digits | else | L,L | find_digits |
| find_first_digit | x | L | found_first_digit |
| find_first_digit | y | L | found_first_digit |
| find_first_digit | z | L | found_second_digit |
| find_first_digit | None | R,R | find_first_digit |
如果未知位是`1`, 那麼過程和7位就夠用了, 否則未知位就是0. 現在, 已經有了都是0的7位過程和, 可以開始做乘法運算了.
現在, 開始準備首次的位與位相乘了. 為了記錄當前是那兩位在做乘法運算, 使用`x`, `y`, `z`進行標記. 其中`x`標記與`y`標記做乘法運算, 若自身相乘, 則標記為`z`. 先通過`find_digits` 回到第一個非數字格. 然後通過`find_first_digit` 跳到第一個做乘法運算的數字格. 並根據標記的不同調用不同的方法.
![image-20201206183908118](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206183908.png)
**當前狀態: `found_second_digit`**
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| find_second_digit | x | L | found_second_digit |
| find_second_digit | y | L | found_second_digit |
| find_second_digit | None | R,R | find_second_digit |
| found_first_digit | 0 | R | add_zero |
| found_first_digit | 1 | R,R,R | find_second_digit |
| found_second_digit | 0 | R | add_zero |
| found_second_digit | 1 | R | add_one |
| found_second_digit | None | R | add_one |
這裡可以看到, 若找到的數字是0, 則直接加0, 因為相乘後的結果必是0. 若相乘之後的結果是1, 則向過程和加1.
若找到的第一個數字是1, 則轉換去尋找第二個數字.
![image-20201206191441486](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206191441.png)
**當前狀態: `add_one`**
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| add_zero | r | Ps | add_finished |
| add_zero | u | Pv | add_finished |
| add_zero | else | R,R | add_zero |
| add_one | r | Pv | add_finished |
| add_one | u | Ps, R,R | carry |
| add_one | else | R,R | add_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](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206192603.png)
**當前狀態: `add_finished`**
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| add_finished | a | R,R | erase_old_x |
| add_finished | else | L, L | add_finished |
| erase_old_x | x | E,L,L | print_new_x |
| erase_old_x | z | Py,L,L | print_new_x |
| erase_old_x | else | R,R | erase_old_x |
| print_new_x | a | R,R | erase_old_y |
| print_new_x | y | Pz | find_digits |
| print_new_x | None | Px | find_digits |
| erase_old_y | y| E,L,L | print_new_y |
| erase_old_y | else | R,R | erase_old_y |
| print_new_y | a | R | new_digit_is_one |
| print_new_y | else | Py,R | reset_new_x |
此時, 加法結束了, 需要移動`x`, `y`, `z`標記, 來標識下一對需要相乘的數字. 簡單設想一下, 若只有一個`z`則將其標記為`y`並將左側標記為`x`即可. 若一個`x`一個`y`, 則將`x`左移一位, 但是當`x`到達最高位時, 需要將`x`重置到最低位, 同時左移`y`. 當`y`到達最左側的時候, 計算結束.
![image-20201206193501669](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206193501.png)
**當前狀態: `find_digits`**
現在, 計算又回到了最初的狀態, 可以開始進行新一輪的計算了. 這次相乘的結果`1*1=1`, 再次向過程和中加一. 結果:
![image-20201206194001393](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206194001.png)
繼續執行後, 需要下面幾個狀態(下面為每次回到`find_digits`狀態的紙帶情況)
| 狀態 | 符號 | 操作 | 切換狀態 |
| --- | --- | --- | --- |
| reset_new_x | None | R,Px | flag_result_digits |
| reset_new_x | else | R,R | reset_new_x |
| flag_result_digits | s | Pt,R,R | unflag_result_digits |
| flag_result_digits | v | Pw,R,R | unflag_result_digits |
| flag_result_digits | else | R,R | flag_result_digits |
| unflag_result_digits | s | Pr,R,R | unflag_result_digits |
| unflag_result_digits | v | Pu,R,R | unflag_result_digits |
| unflag_result_digits | else | | find_digits |
![image-20201206194354998](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206194355.png)
![image-20201206203909733](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201206203909.png)
可以看到, 當`x`重置的時候, 下一次位與位相乘的結果需要加到過程和的第二位上, 因此, 需要對過程和的內容做少許修改: 第一個`s`或`v`變成`t`或`w`, 剩下的`s`或`v`變成`r`或`u`. 為了下一次計算的時候, 能夠將結果加到對應的位置上, 就是下一次相乘結果的相加位要向後一格, 在做加一操作的時候, 只識別`r`, `u`, 所以之後的識別符號還需要重置.
操作已經簡單的走了, 是時候將所有狀態放出來了.
| 狀態 | 符號 | 操作 | 切換狀態 |
| ---- | ---- | ---- | ---- |
| carry | r | Pu | add_finished |
| carry | u | Pr,R,R | carry |
| carry | None | Pu | new_digit_is_zero |
| new_digit_is_zero | a | R | print_zero_digit |
| new_digit_is_zero | else | L | new_digit_is_zero |
| print_zero_digit | 0 | R,E,R | print_zero_digit |
| print_zero_digit | 1 | R,E,R | print_zero_digit |
| print_zero_digit | None | P0,R,R,R | cleanup |
| new_digit_is_one | a | R | print_one_digit |
| new_digit_is_one | else | L | new_digit_is_one |
| print_one_digit | 0 | R,E,R | print_one_digit |
| print_one_digit | 1 | R,E,R | print_one_digit |
| print_one_digit | None | P1, R,R,R | cleanup |
| cleanup | None | | new |
| cleanup | else | E,R,R | cleanup |
其中`carry`的操作就是進位操作, 當遇到符號`1`時, 將其改為`0`繼續進位, 當遇到符號`0`的時候, 則改為`1`, 結束. 若遇到空內容, 說明計算產生第8位了, 則未知位必為0, 直接結束計算.
從上面看到, 還有一種結束情況就是`y`走到頭了, 這時沒有產生進位, 說明未知位為1.
剩餘的幾個狀態就一帶而過了, 未知位是1, 未知位是0, 以及`cleanup`清理所有過程和的內容.
整個乘法會以兩種情況結束:
1. 當進位產生新的位數時, 結束. 未知位是0
2. 當`y`標記下一步走到頭了, 結束. 未知位是1
至此, 已經完成了計算$\sqrt{2}$的操作. 這個狀態可以周而復始的一直計算下去. 不再繼續延時了, 感興趣的可以自己按照上面的狀態表算一遍. 看了上面命名的狀態, 有沒有覺得和函式很像呀.
其實其原理並不複雜, 就是進行0和1的簡單嘗試, 然後根據結果的大小來決定後一位是什麼內容. 但我還是被圖靈能夠在一維的紙帶上實現的操作折服了.
## 方法集
看了上面的內容, 有沒有覺得少了點什麼? 那一個個的不就是函式嘛.而圖靈下一步要做的, 就是要組建一格常用表集, 作為基礎來搭建一些更為複雜的表. 更形象的說法就是封裝函式. 同時, 函式的封裝也方便他後面構建更大的程式. 關於函式的概念就不在贅述了, 天天用的也不少了. 就給出圖靈的表達形式, 大家自然能夠看懂.
回看一下上面的`new_digit_is_zero`和`new_digit_is_one`兩個函式, 函式化之後的標識:
| 狀態 | 符號 | 操作 | 下一個狀態 |
| -------------- | ---- | ----- | -------------- |
| print_digit(A) | 0 | R,E,R | print_digit(A) |
| print_digit(A) | 1 | R,E,R | print_digit(A) |
| print_digit(A) | None | P(A),R,R,R | cleanup |
很好理解哈, 就不解釋了. 同時, 其引數也可以傳遞狀態. 將這個基礎表稱之為骨架表. 可以看的出來, 所有的骨架表都可以轉換成不帶引數的形式. 到這裡, 其實和現在的函數語言程式設計思想已經很接近了有木有.
舉個栗子:
| 狀態 | 符號 | 操作 | 下一個狀態 |
| --- | --- | --- | --- |
| f(S, B, a) | α | L | f1(S, B, a) |
| | else | L | f(S, B, a) |
| f1(S, B, a) | a | | S |
| | None | R | f2(S, B, a) |
| | else | R | f1(S, B, a) |
| f2(S, B, a) | a | | S |
| | None | R | B |
| | else | R | f1(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) | | E | S |
| 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) | Any | R | g(S) |
| | None | R |g1(S)|
| g1(s) | Any | R | g(S) |
| | None | | S |
| g1(S, a) | a | | s |
| | else | L | g1(S, a) |
| g(S, a) | | | g(g1(S, a)) |
其中單引數的函式 g 和單引數的函式 g1配合, 將掃描格移到最右側.
**在結尾列印**
| 狀態 | 符號 | 操作 | 下一個狀態 |
| --- | --- | --- | --- |
| pe(S, b) | | | f(pe1(S, b), S, α) |
| pe1(S, b) | Any | R,R | pe(S, b) |
| | None | Pb | S |
其中, 這個函式有一個假設, 就是最左側有兩個連續α, f 函式先將掃描格移動到最左側α, 然後右移一格開始尋找, 這時當前格就是α.
**在結尾列印兩個字元**
| 狀態 | 下一個狀態 |
| ------------ | --------------- |
| pe2(S, a, b) | pe(pe(S, b), a) |
直接先在結尾列印 a, 然後在在結尾列印 b
**增強 find 函式**
f 函式在找到所需字元後, 將掃描格向左或向右移動.
| 狀態 | 操作 | 下一個狀態 |
| --- | --- | --- |
| l(S) | L | S |
| r(S) | R | S |
| 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 進行遍歷並將其擦除. 而複製擦除下一個字元, 又要重複這些操作. 如果在第一次找到的時候, 就順手擦除, 然後再進行復制, 豈不更好.
不過, 想必圖靈在當時並沒有考慮效率的問題, 或者說他並不關心效率的好壞, 畢竟連這臺機器都是想象出來的. 現在, 圖靈已經可以構建一格函式庫了, 類似與現在的系統庫, 想必能夠構造更為強大的機器了.
## 數字化
接下來, 圖靈對他的表格進行了重新定義. 他先是證明了所有狀態都可以拆解成如下所示的三個狀態:
| 狀態 | 符號 | 操作 | 符號 | 標識 |
| --- | --- | --- | --- | --- |
| qi | Sj | PSk, L | qm | N1 |
| qi | Sj | PSk, R | qm | N2 |
| qi | Sj | PSk | qm | N3 |
其中 Sk 用來表示符號. 規定:
* S0 : 表示空格
* S1 : 表示0
* S2 : 表示1
* 其他: 一些自定義符號
其中的操作是:
* N1 : 列印並左移
* N2 : 列印並右移
* N3 : 列印
疑問, 改成這樣真的能夠表示之前的所有操作麼? 舉例一下 :
* 擦除操作: 既列印空格. PS0
* 左移操作: 既列印格子原本的符號.
而之前的一些較長的操作, 通過拆解也可以拆成這樣的基本形式. 然後, 圖靈定義了這麼一格五元組:
**qi SjSkLqm** 用來直接表示上方的 N1 . 無用說也能和上面的表格對上吧. 有沒有想到圖靈要做什麼? 這裡每一個五元組, 都對應一個操作, 如果將多個五元組連起來, 並用分號隔開, 是不是就能完整的描述一個程式了.
至此, 他的狀態表已經經過了一次轉換, 變成了下標的形式. 接下來, 圖靈要把下標去掉. 替換:
* qi -> D 後面 i 個 A
* Sj -> D 後面 j 個 C
如此一來, 他的機器就只剩下以下幾個字元: `D`, `A`, `C`, `L`, `R`, `N`, `;`. 其中`N`表示不移動. 圖靈將這樣的描述稱為*標準描述*.
再然後, 圖靈將僅存的這7個字元, 用1-7的數字來表示. 既: 1(A), 2(C), 3(D), 4(L), 5(R), 6(N), 7(;). 那麼, 將得到一個完全由數字組成的完成程式. 而這些數字連起來, 就是一個比較大的整數, 也就是說, 圖靈用一個整數來完全表示了他的機器. 這個數字被圖靈稱為描述數.
也就是說, 一個描述數可以唯一確定一個程式, 而一個程式可以對應多個描述數(因為狀態的順序是可以隨意更換的). 同時, 這也說明通過列舉所有的整數, 可以得到所有可能的計算序列.
其實與現在的程式有著異曲同工之妙, 現在的程式也不過是一串二進位制的數字.
## 可程式設計通用機
接下來, 圖靈描述了一個可程式設計的通用機器, 將程式1的描述數放到紙帶的開頭, 機器 A 通過讀取並完全復刻所有格局來實現程式1的功能. 這臺機器可以通過讀取不同的輸入紙帶, 來實現不同程式的功能.
同時, 圖靈證明了這樣一個機器的可行性. 現在, 假設需要執行的程式是之前的交替列印0和1的程式, 其狀態表如下:
| 狀態 | 符號 | 操作 | 下一個狀態 |
| ---- | ---- | ------ | ---------- |
| b | None | P0 | b |
| | 0 | R,R,P1 | b |
| | 1 | R,R,P0 | b |
轉換成通用格局之後:
| 狀態 | 符號 | 操作 | 下一個狀態 |
| ---- | ---- | ---- | ---------- |
| q1 | S0 | PS1,R | q2 |
| q2 | S0 | PS2,R | q1 |
很簡單, 列印0然後列印1, 交替進行. 將通用格局轉換成可描述符號:
* q1S0S1Rq2: DADDCRDAA
* q2S0S2Rq1: DAADDCCRDA
輸入紙帶如下所示(一行顯示不下, 但他還是一維紙帶哦):
![image-20201212171922404](https://hujingnb-blog.oss-cn-beijing.aliyuncs.com/img/20201212171922.png)
每一條指令由五個連續部分組成:
1. D 接 n 個 A: 表示狀態, 最少由一個 A
2. D 接 n 個 C: 表示識別符號
3. D 接 n 個 C: 表示在掃描格列印的符號
4. L/R/N: 表示掃描頭的移動方向
5. D 接 n 個 A: 表示下一個切換的狀態.
接下來的證明過程, 就有些超出我的理解了, 感興趣的朋友可以自行鑽研一下, 我是看了好久, 也沒搞懂.
至此, 圖靈的這臺機器, 其實已經有了現代計算機的雛形了. 而我, 也被這幾十年前偉大的思想折服了. 牛批...
同時, 圖靈的論文後面還使用這臺通用機器進行了一些證明, 不過, 那並不是我所關心的