原本我也是學習如何將正則表達式一步步化到DFA,搜索發現很多不是死板的定義,就是跨度太大,所以我決定用一道例題,看看它是如何轉化的,本次以正則表達式:(a|b)*(aa|bb)(a|b)* 為例。
我看到和多人會介紹將正則表達式轉化為NFA的規則,為了便於理解我也選擇簡單說一下,正則表達式轉化為NFA會有基本的一個開始,一個結束,結束為雙圈,其余的都是單圈。圓圈里的是狀態,圈到圈的線上代表的一個狀態到另一個狀態的轉化條件。對於一個識別空字符的NFA便是:
它不需要識別任何字符便可以從開始狀態轉化為終結狀態。所有從開始狀態最從轉化為終結狀態的線路上的字符串的集合便是這個NFA和正則表達式所表示字符串集合。簡單的對於一個終結符a:
假如我們現在有兩個NFA,一個表示識別字符s,記為 N(S); 一個識別字符t,記為 N(T)。那么我們如何表示一個識別字符串 st 的NFA呢?它的規則是將兩個NFA連接,只保留一個初始狀態,一個終結狀態。
如何表示識別字符 s 或者字符 t 的NFA呢?
很容易理解,無論選擇哪條路,都可以從初始狀態轉化為終結狀態,所以這個NFA可以識別 N(S) 或者 N(T) 能夠識別的字符串集合。現在我們繼續想一下如何識別n個字符 s 呢?我們可以將N(S)的首尾相連,這樣就會循環一次或多次,就可以識別一個或者多個字符 s ,那么零個的情況呢?我們可以直接將 N(S) 的前后相連,這樣便也可以選擇直接跳過 N(S) 了,如下圖所示:
上面便是基本的構建NFA的規則,下面給出幾個基本的例子:
對於 a|b:
對於 (a|b)*:
對於(a|b)*c:
其實在自己做題的時候不需要一步步畫這么麻煩的圖,上面這么多 ‘空’,看着就讓人頭大了。下面以(a|b)*(aa|bb)(a|b)* 為例,根據正則表達式畫出稍微看着簡單的nfa圖:
根據的規則是從正則表達式開頭開始,遇到 ' * '就先畫一個圈,前后兩個空,根據 ’ * ‘包含的內容在這個圈的周圍繼續畫圈和線。而中間的連接什么的就不用要 ’空‘ 了。再舉一個例子 (a*b)*a*:
現在繼續以(a|b)*(aa|bb)(a|b)* 為例,已經轉化為 nfa,如何轉化為 dfa呢?這里我們使用子集法,我們先給出結果,然后再分析過程是怎樣的。
左邊是開始的集合元素,右邊是先經過a或者b之后可以經過n個 ’空‘ 到達的元素集合。第一個開始的元素集合是X只經過 ’空‘可以到達的元素,由上面的圖我們可以知道X只經過 ’空‘ 可以到達1,2. 所以一開始為:X,1,2;
X不能經過a|b
1經過a再經過n個’空‘可以到達:1,2
2經過a再經過n個’空‘可以到達:3
1經過b再經過n個’空‘可以到達:1,2
2經過a再經過n個’空‘可以到達:4
所以左邊在經過a|b后得到的右邊分別為:{1,2,3} ; { 1,2,4}
接下來左邊的元素集合為右邊出現的順序的元素集合,即{1,2,3};{1,2,4};.......,需要注意的是,當右邊出現 ’空‘,左邊也得出現 ’空‘,且’空‘在任何條件下得到的都是’空‘,不能把這一行省略不寫。現在我們要為上面的元素集合改改名字:
------------------>>>
根據上面的圖我們可以就畫出dfa了:
我們可以看到上面的圈中有一個的,也有兩個的,前面我們說過兩個代表終態,那么如何確定應該是一個還是兩個?我們可以看改名字的那張圖,在nfa中,Y是終態,此時在dfa中,只要包含Y的就是兩個圈,所以D,E,F,G都是兩個圈。接下來就該簡化我們得到的dfa了,簡化的第一步是先將上面的狀態分為兩類,一個圈的和兩個圈的:{A,B,C},{D,E,F,G},我們用{A,B,C}a={B,D}表示:{A,B,C}在a的條件下可以得到{B,D},則:
{A,B,C}a={B,D},{B,D}不是{A,B,C}或者{D,E,F,G}的子集,所以要把B分出來{A,C},{B},{D,E,F,G}
{A,C}a={B}是{B}的子集,{A,C}b={C,E}不是{A,C},{B},{D,E,F,G}其中某一個子集,要把C分出來{A},{C},{B},{D,E,F,G}
{D,E,F,G}a={D,G},是{D,E,F,G}的子集,不用分;{D,E,F,G}b={E,F},是{D,E,F,G}的子集,不用分
所以{D,E,F,G}四個可以合成一個D:
上面就是化簡后的dfa,到這里我們就算正則表達式->nfa->dfa->化簡的dfa 就結束了。