剛剛拜讀了一本書, 《圖靈的祕密》. 該書介紹了圖靈的論文《論可計算數及其在判定上的應用》, 其指出: 一個擁有鉛筆, 紙和一串明確指令的人類計算者, 可以被看做是一種圖靈機
. 那麼圖靈機
是什麼呢? 是圖靈為了描述可計算數
而引出的一個虛構的可以做一些簡單操作的計算機器. 儘管這個機器很簡單, 但圖靈斷言它再功能上等價與一個進行數學運算的人.
先提個小醒, 文章有些長, 而且還比較枯燥.
當然了, 那些數學證明並不是我關心的, 我關心的是這個圖靈機
. 圖靈為了說明他的數學理論, 描述了一臺機器, 而這臺機器, 看過之後發現其實已經算是現代計算機的雛形了, 雖然他的出發點和發明計算機並不沾邊吧. 先簡單介紹一下這臺機器.
這是一個配有一條紙帶的機器, 紙帶由一個個方格隔開, 圖靈只使用其中相見的格子列印資料, 暫且稱之為數字格, 數字格之間的用來輔助計算. 大概就這麼一個紙帶:
而這臺機器對操作的定義由一張狀態表來決定:
狀態 | 符號 | 操作 | 狀態切換 |
---|---|---|---|
其中每個狀態(原文為: 格局)對應的符號不同, 會執行不同 的操作並切換的對應的下一個狀態. 可以將狀態類比成不同的函式, 再不同的選擇分支下執行不同的邏輯並呼叫不同的函式. 下面給出一些符合和操作的定義:
符號
- 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.展示紙帶的初始狀態
其中紅色方塊標記機器當前的掃描方格.
2.當前狀態: b
, 列印0並向右移動一格, 切換狀態: c
3.當前狀態c
, 向右移動一格, 切換狀態: e
4.當前狀態e
, 列印0並向右移動一格, 切換狀態: f
5.當前狀態 f
, 向右移動一格, 切換回狀態: b
此時, 切換回了初始的狀態, 然後周而復始的列印下去. 當然, 上述狀態是可以進行簡化的, 通過當前方格的不同符號, 可以進行不同的操作, 簡化後的狀態表:
狀態 | 符號 | 操作 | 狀態切換 |
---|---|---|---|
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
可以發現, 狀態b
在初次執行之後, 就再也沒有出現過了, 所以可以將其形容為初始化的操作.
2.當前狀態e
, 符號0
,直接切換狀態: q
3.當前狀態q
, 符號0
, 操作: R,R
, 切換狀態q
4.當前狀態q
, 符號0
, 操作: R,R
, 切換狀態: q
5.當前狀態 q
, 符號None
, 操作: P1, L
, 切換狀態p
可以看到, q
狀態的操作就是向當前序列的尾部新增一個1.
6.當前狀態 p
, 符號None
, 操作: L,L
, 切換狀態p
這不的操作就是向左移動到第一個不為空的非數字格. 省略一步, 結果:
7.當前狀態p
, 符號a
, 操作: R
, 切換狀態: f
從這裡可以看出來, p
狀態其實是充當各個狀態之間的排程員角色的.
8.當前狀態f
, 符號0
, 操作: R,R
, 切換到狀態f
這裡, 可以觀察到, 狀態f
的作用是向數字的結尾列印0, 然後左移兩格, 切換到e
狀態. 跳過中間步驟:
**9.當前狀態e
, 符號1
, 操作: R,Px,L,L,L
, 切換狀態e
. **
簡單分析, e
狀態的操作是在連續1
的每一個右邊列印x
, 之道遇到0
, 切換到q
狀態
10.當前狀態q
, 符號0
, 則向尾部新增一個1, 切換狀態p
**11.當前狀態p
, 符號None
**
通過前面分析, p
狀態向左找到第一個不為空的非數字格, 在這裡則是x
. 然後會擦除x
並切換到q
狀態, 既向右列印1
. q
狀態執行完畢後的紙帶狀態如下:
此時會再次切換回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 |
假設此時, 紙帶的狀態:
現在對各個數字位進行標記.
狀態 | 符號 | 操作 | 切換狀態 |
---|---|---|---|
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
.
當前狀態: 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.
當前狀態: 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
跳到第一個做乘法運算的數字格. 並根據標記的不同呼叫不同的方法.
當前狀態: 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, 則轉換去尋找第二個數字.
當前狀態: 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)並同時處理進位.
當前狀態: 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
到達最左側的時候, 計算結束.
當前狀態: find_digits
現在, 計算又回到了最初的狀態, 可以開始進行新一輪的計算了. 這次相乘的結果1*1=1
, 再次向過程和中加一. 結果:
繼續執行後, 需要下面幾個狀態(下面為每次回到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 |
可以看到, 當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
清理所有過程和的內容.
整個乘法會以兩種情況結束:
- 當進位產生新的位數時, 結束. 未知位是0
- 當
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
- 左移操作: 既列印格子原本的符號.
而之前的一些較長的操作, 通過拆解也可以拆成這樣的基本形式. 然後, 圖靈定義了這麼一格五元組:
qiSjSkLqm 用來直接表示上方的 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
輸入紙帶如下所示(一行顯示不下, 但他還是一維紙帶哦):
每一條指令由五個連續部分組成:
- D 接 n 個 A: 表示狀態, 最少由一個 A
- D 接 n 個 C: 表示識別符號
- D 接 n 個 C: 表示在掃描格列印的符號
- L/R/N: 表示掃描頭的移動方向
- D 接 n 個 A: 表示下一個切換的狀態.
接下來的證明過程, 就有些超出我的理解了, 感興趣的朋友可以自行鑽研一下, 我是看了好久, 也沒搞懂.
至此, 圖靈的這臺機器, 其實已經有了現代計算機的雛形了. 而我, 也被這幾十年前偉大的思想折服了. 牛批...
同時, 圖靈的論文後面還使用這臺通用機器進行了一些證明, 不過, 那並不是我所關心的內容.