本文參考了:
- 談談狀態機
- Introduction to the Theory of Computation : Lecture 1(stonehill college)
- Introduction to the theory of computation
- 有限狀態機通常在什麼地方被用到?
- softwareengineering.stackexchange.com/questions/4…
簡介
這是《計算理論101》系列的第一篇,將從最基本的有限狀態機講起。主要參考的課程和書籍有: Introduction to the Theory of Computation 3rd Edition 和 【Stonehill college CS】Introduction to the Theory of Computation 。
為什麼要學習『計算理論』?
如 Stonehill college 的 Professor of Computer Science Shai Simonson所說,這可能是計算機課程中最抽象的一門了,但卻是任何 computer scientist(我覺得可以延生至任何真正熱愛 CS 的人)至少需要了解的一門課。這門課不會教你如何寫程式碼,也不會教你如何做一個計算機,而是著重於『電腦科學』中的『科學』兩個字,去了解電腦科學發展幾十年來前人閃耀的思想。
還有一點,我認為也是很重要的,那就是學習新知識那份最單純的快樂。
好了,廢話不多說,開始正題~
什麼是有限狀態機
以一個程式設計師的角度,我的理解就是記憶體有限的一個機器,上面定義了一些函式,可以從一個狀態跳轉到另一個狀態。嚴格的數學定義,一個有限狀態機可以定義為一個五元組,如下圖表示(來源於Introduction to the Theory of Computation 3rd Edition P35):
可以用圖直觀地表示:下圖這個狀態機,圓圈表示的是可能出現的狀態,可能輸入的值為0和1,裝換函式就是那些箭頭,開始狀態為 q1,accept 狀態為 q2(用雙圓圈表示)。
一個有限狀態機的定義其實就是這麼簡單。
有限狀態機應用
有限狀態機一個最顯然的特點就是記憶體有限,無法記憶所有的歷史輸入,所以它能夠解決的問題是有限的。至於什麼問題能夠解決,什麼不能,後面再說。先來看看有限狀態機的應用。
嵌入式領域:自動門
這是《Introduction to the Theory of Computation 3rd Edition》書中提到的一個例子。
下圖中,門口和門背各有一個感應器(front 為門口,rear 為門背)。門的控制開關一共有兩個狀態:OPEN 和 CLOSED,輸入一共有四種情況:FRONT(門口有人)、REAR(門背有人)、BOTH(門口門背都有人)、NEITHER(兩邊都沒人)。
有一個圖表示狀態轉換過程為:
稍微解釋一下:
- 處於 CLOSED 狀態,只有前面的 pad 檢測到有人時才會開啟,從 CLOSED 狀態變為 OPEN 狀態,其餘情況下維持狀態不變。
- 處於 OPEN 狀態時,只有當兩邊都沒有檢測到人時,才轉換為 CLOSED 狀態。
我對於這個門的設計表示懷疑,處於 CLOSED 狀態時,難道 REAR PAD 檢測到人不應該變成 OPEN 狀態嗎?也許是我哪裡理解得不對,有童鞋知道的歡迎指出。不過這一點不影響我們對於有限自動機的理解。
這個自動門其實就可以看作是一個最簡單的計算機了,它只有一個 bit 的儲存空間,可以記錄當前門的狀態是OPEN 還是 CLOSED。類似的還有電梯, 飲料機 等等。
事實上,最開始我們骨灰級的程式設計師(電腦科學家)們,面對的就是類似的情況,儲存空間極其有限。現在的很多嵌入式裝置,記憶體同樣非常有限,所以有限狀態機還是有用武之地的。
程式設計領域:正規表示式
這裡推薦一個網站: regexper.com/ ,能夠非常直觀地將正規表示式還原為一個 finite state machine。另外開源地址如下: gitlab.com/javallone/r… 。
舉幾個正規表示式例子:
匹配手機號:/^1(3|4|5|7|8)\d{9}$/
:
匹配郵箱:^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$
匹配 IP 地址:^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
這裡不深入程式碼層,不然這篇文章就講不完了。有興趣的童鞋自己 Google 一下,後面有機會可能會再次講到。
其他
這個 stackexchange 的連結裡面提到了很多: softwareengineering.stackexchange.com/questions/4…
設計有限狀態機
例項一
設計一個FSM,能夠識別被3整除的二進位制字串。比如accept 1001
,reject 1101
。
首先,我們可以把所有餘數(reminder)的可能性當作狀態,也就是三個狀態:0,1,2,其中0時 accept 狀態。
然後分析一下狀態轉換是怎麼樣的?二進位制數,末位新增一個0表示乘以2,末位加1表示乘以2再加1,對於餘數,同樣是乘2或者乘2加1,只不過餘數會『進位』。
- 從起始狀態開始:最開始沒有輸入,也就是0。
- 對於狀態0:輸入為0時維持不變,為1會跳轉到1。
- 對於狀態1,輸入為0時,餘數要乘以2,變成了2;輸入為1時,餘數乘2加1,結果變為0(3)。
- 對於狀態2:輸入0乘以2,變為1(4);輸入1乘以2再加一,變為2(5)。
這樣就做完了,一個用普通演算法很難解決的問題,用FSM是不是很簡單?會設計識別能被3整除的 FSM,接下來被4、5、6、7、8整除的是不是也會了?這裡,被2^k(k=1,2,3,4)整除還有一個更簡單的方法:轉換為判斷末位至少有k 個0。
動手來畫一畫:
例項二
設計一個 FSM,能夠識別 能被4整除的二進位制字串(末尾至少有兩個0)。 這裡把狀態設計為目前為止末尾收到了幾個0,一共有三個狀態:0個0,1個0,2個0。
有這個還可以推廣到模式字串識別,比如要識別特定子字串 ,請看下面這個例子。
例項三
設計一個 FSM,能夠識別子字串abcab
。
為了簡單起見,這裡把圓圈省略掉了,同時把中間的某些節點的狀態轉換忽略掉了。
- 最開始,狀態為未匹配任意字元。
- 在起始狀態:輸入 a,跳轉到 a;輸入其他狀態保持不變。
- 在狀態 a:輸入 b 跳轉到 ab;輸入 a 維持狀態不變;輸入其他跳回起始狀態。
- 在狀態 abc:輸入 a 跳轉到 abca;否則跳回起始狀態。
- 在狀態 abca:輸入 b 跳轉到 abcab,也就是accept 狀態;輸入 a 跳轉回 a(注意不是跳轉回起始狀態,因為現在是 abcaa,也就相等於 a);輸入其他跳轉回起始狀態。
- 在狀態 abcab:無論輸入什麼,都維持不變,因為當前已經匹配成功了。
總結一下
狀態機的定義非常簡潔,但是功能很強大,而且非常有意思不是嗎?
碼字很辛苦,圖文並茂更辛苦,點個贊鼓勵一下~
廣告時間,歡迎大家關注我的微信公眾號。同時本文同步於 github: github.com/liaochangji…