# Pushdown Automata / PDA 跟 NFA 有點像,但多了 stack 這個物件來幫我們記錄更多東西,也讓我們可以辨識一些不正常語言。 Pushdown Automata 跟 CFG 的 power 是等價的,或者說他們的能力是一樣的。 這給了我們方便的判斷, 因為如果我們想要證明某個語言是 CFL,則我們要嘛建構出一個 Pushdown Automata,要嘛產生出一個 CFG。 當然,有些語言比較容易由 CFG 描述,有些則是 Pushdown Automata。 ## PDA 的 stack 他就是個 stack,必須符合先進後出的規則;寫入是 push,寫出是 pop,每次寫入一個 symbol。 堆疊的好用之處在於,它可以幫我們記住「無限的資訊」,因為堆疊沒有容量限制,所以對於 $\{0^{n}1^{n}|n\ge0\}$ 這種語言,就可以用 PDA 來辨識。 :::warning 每次讀到 0,就塞一個 0 到堆疊;每次讀到 1 ,就從堆疊吐一個 0 出來: - 如果讀完 input 後堆疊是空的,允許該字串 - 如果以下條件滿足,則拒絕該字串 - 堆疊是空的,但是卻讀到了 1 - 堆疊存有 0,但是卻讀完了字串 - 讀入的字串中,兩個以上的 0 後面都跟隨著一個以上的 1 - 像是 0101 ::: # Deterministic 跟 Nondeterministic 跟有限自動機一樣, PDA 也有分這兩種,也就是讀到一個 input 後是一種輸出狀態或是多種輸出狀態。 但是要注意,DPDA 跟 NDPA 的 Power 不一樣,他們能力有所不同,而只有 NPDA 跟 CFG 的能力是一樣的,所以下面我們就只介紹 NPDA,**下面提到的 PDA 都是指 NPDA**。 :::warning 之前有提到 NFA 跟 DFA 是等價的,因為他們之間可以進行轉換,但是 NPDA 跟 DPDA 就不是等價的,有些語言只有 NPDA 可以辨識,但是 DPDA 不行。 ::: # NPDA 標準定義 一個 NPDA 是個 6 tuple: $$ (Q,\Sigma,\Gamma,\delta,q_0,F) $$ 1. $Q$ 是狀態的集合 2. $\Sigma$ 是 input 的字母集合 3. $\Gamma$ 是 stack 的字母集合 4. $\delta:Q\times \Sigma_{\varepsilon} \times \Gamma_{\varepsilon}\rightarrow \mathcal{P}({Q\times \Gamma_{\varepsilon}})$ 是轉換函數 5. $q_0\in Q$ 是起始狀態 6. $F\subseteq Q$ 是允許狀態們 - 因為 stack 會存放字母,所以有其專門的代號 $\Gamma$。 - 所以也會有包含空字串的代號形式: - $\Sigma_{\varepsilon}=\Sigma\cup\{\varepsilon\},\ \Gamma_{\varepsilon}=\Gamma\cup\{\varepsilon\},$ - 轉換函數的定義域是 $Q\times \Sigma_{\varepsilon} \times \Gamma_{\varepsilon}$ - $\Gamma_{\varepsilon}$ 的部分是當前 stack 的 top - $\Sigma_{\varepsilon}$ 的部分是當前的 input - $\varepsilon$ 的行為請見下面 - 轉換函數的值域是 $\mathcal{P}({Q\times \Gamma_{\varepsilon}})$ - 轉換函數的一個輸出,告訴 NPDA 下一步要移動到哪個狀態,以及在 stack 上寫入哪個字元,也就是 ${Q\times \Gamma_{\varepsilon}}$ - 但因為是 Nondeterministic,所以可以有很多種輸出,才因此是 Power set ## 允許一個字串 如果 NPDA 允許一個字串: - 令允許字串為 $w=w_{1}w_{2}...w_{m},w_{i}\in\Sigma_{\varepsilon}$ - 與其對應的狀態是 $q_{0},q_{1},...,q_{m}\in Q$ - 以及對應每個狀態當下,堆疊中存放的字串 $s_{0},s_{1},...,s_{m}\in \Gamma_{\varepsilon}^{*}$ - 注意,每個 $s_{i}$ 都是字串 要滿足下面三個條件: - $r_{0}=q_{0}$ 也就是第一個狀態要是起始狀態 - $s_{0}=\varepsilon$ 也就是一開始堆疊內要是空的 - 對於 $i=0,...,m-1$,$(r_{i+1},b)\in\delta(r_{i},w_{i+1},a)$ - 其中 a 跟 b 分別代表讀入前後 top 的 symbol - 並且 $s_{i}=at,s_{i+1}=bt,t\in\Gamma^{*}$ - 這告訴我們這個 NPDA 正確的按照當前狀態、當前堆疊以及下一個 input symbol 的內容進行移動 - $r_{m}\in F$ 也就是最後一停在允許狀態的集合 :::warning 畫成狀態圖的時候,會在箭頭旁邊寫上 $a,b\rightarrow c$ 來表示 $$ \Huge \overset{a,b\rightarrow c}{\longrightarrow} $$ - $a$ 一樣是當前讀到的 symbol - $b\rightarrow c$ 則代表 top 從 $b$ 變成了 $c$ - 也就是讀取 $b$ 之後 pop 掉 $b$,並再把 $c$ 寫入。 ::: ## 空字串的行為 如果輸入的 top 是空字串,其意義是「不讀取 top」。 而這三個 symbol 都可以為空字串: - $a=\varepsilon$ 代表不讀入任何 input 就往下一個狀態邁進 - $b=\varepsilon$ 代表不讀取 top 也不做 pop,直接寫入 $c$ - $c=\varepsilon$ 代表讀取 top 並做 pop 後,不寫入任何 symbol ## 注意事項 正式的 PDA 定義中,我們沒有明確的機制來檢驗當前堆疊是否為空,但是我們可以不正式地藉由「特殊字元 $」,放到堆疊內,這樣只要之後再看到 $ 出現在堆疊內,代表堆疊已經空了。 例如下面就是 $\{0^{n}1^{n}|n\ge0\}$ 這種語言的 NPDA,可以看到我們在起始狀態 $q_1$ 無條件的塞入 \$ 符號 ![](https://drive.google.com/uc?id=1BgzEr7FxQW5rZ26hBu2C3DpBluHNkT5H&export=download) >我忘記畫指向起始狀態的箭頭了 同樣的正式的 PDA 定義中也無法確認是否到輸入的結尾了,但是上圖的 PDA 可以做到,因為我們多加了 \$ 之後,結束時的判斷也跟 \$ 有關,要走到 $q_4$ 的前提是堆疊內只剩下 \$,否則永遠到不了。 :::info 可以注意到堆疊先進後出的約束在此發揮了作用 ::: # 例子1 $$ {a^ib^jc^k\ |\ i, j, k ≥ 0\ and\ i = j\ or\ i = k} $$ >注意,這跟之前那個本質上混淆的語言 $\{a^{i}b^{j}c^{k}\ |\ i=j\ or\ j=k\}$ 不一樣 ![](https://drive.google.com/uc?id=1urMf2v1UQ1QoKu2Go3Duo9wskjRX__2n&export=download) - 我們要檢查的是 a 跟 b 或 c 的數量有沒有一樣,所以堆疊內存放的是 a,也就是 $q_2$ 在做的事情。 - 此時存完後會無條件分出兩支,可以看到都是 $ε,ε\rightarrow ε$ - 上路是在檢查 a 跟 b,如果到了允許狀態,還要記得後面可能會有 c,也就是 $q_4$ 的 $c,ε\rightarrow ε$ - 下路首先要把 b 給抽乾,所以才會先經過 $q_5$ :::warning 可以注意到 $q_2,q_3,q_5,q_6$ 他們的結構都是一個指向自己的箭頭,搭配 $ε,ε\rightarrow ε$,這代表說這幾個狀態「隨時都會去猜」當前有沒有可能要進入下一個狀態。 以 $q_2$ 來說就是隨時都會去猜 a 讀完了沒。 ::: # 例子2 這次的語言是: $$ \{ww^{\mathcal{R}}\ |\ w\in\{0,1\}^{*}\} $$ ![](https://drive.google.com/uc?id=1lVeRc-7Hwo-kCohS3EzubvUbQ6x0Elb0&export=download) 一樣可以看到 $q_2$ 會隨時去「猜」到 $q_3$ 了沒,也就是到了 $w$ 跟 $w^{\mathcal{R}}$ 的交界處了沒。 :::warning 可以注意到,因為現在從某個狀態到下一個狀態會由三個值決定,所以更好的表達我們想要的結果。 例如在 $q_3$ 指向自己的箭頭中, $0, 0\rightarrow \varepsilon$ 必須要求除了讀到 0,還要求top 也要是 0,因此只要 top 不是 0 的話就會被判定是不允許的輸入。 ::: --- # PDA 跟 CFG 的等價 接下來我們要證明 PDA 跟 CFG 具有相同的力量;跟之前證明 NFA 和 DFA 一樣,我們要嘗試從 PDA 轉換回 CFG,還有從 CFG 轉換回 PDA。 >別忘記這裡的 PDA 是指 NPDA。 總之我們要嘗試證明: :::info 一個語言是「context free」,若且為若某些 PDA 辨識這個語言 ::: # 左到右 :::warning 如果一個語言是「context free」,則某些 PDA 辨識這個語言。 ::: 先宣告變數: - A 是內容自由的語言 - G 是 A 的文法 接著要嘗試從 G 轉換成一個等價的 PDA,我們叫他 P。 我們現在要做的事情是,我們想要創建一個 PDA 可以模擬「G產生出某個字串的過程」。 因為「G」是一個文法,他可以有各式各樣的 derivation,一般來說很難去判斷到底某個字串能不能從起始變數來得到,但是 NPDA 因為其 Nondeterministical 的特性,讓他可以去猜各種無限的可能,因此可以來模擬 G 生成某個字串的過程。 ## 猜的方法 首先在起始狀態時,將起始變數 A 放進 stack 後進入一個「循環狀態」,這個循環狀態會去看 top 內容是甚麼來決定下一步。 因為一開始就放進起始變數,所以我們可以知道此時「top 是一個變數」,接著我們根據 G ,將起始變數替換成其他內容: $$ A\rightarrow w,\ w\in (V_G\cup \Sigma_G)^{*} $$ 所以就只要把 top 內的起始變數替換成 w 就行;而可能有很多種這樣的規則,這剛好就是 PDA 可以去「無限的猜」的部分。 假如**有某條分枝將 A 替換成了一個終端 a**: $$ A\rightarrow a $$ 此時 top 變成了 a,下一次讀到的 top 就是一個終端,這時就只要將 a 跟讀入的字串進行比對就可以。 如果剛好我們讀到的字串就只有 a,我們就可以將 a 從 stack 移除,這樣一來 stack 內就空了,也就代表我們讀入的字串確實可以由 G 所產生,所以就可以進入允許狀態。 上面就是我們使用 PDA 來去「模擬」G 產生某個字串的過程。 ## P 的結構 1. 首先,先將 $ 跟起始變數放進 stack 2. 重複下面流程 - 如果 top 是一個變數 A,nondeterministically 的選擇一個可以特換掉 A 的規則 - 如果 top 是一個終端,不對 stack 做任何操作,只單純讀入一個 input 做比對。 - 如果 top 是 $,進入允許狀態;也就是代表我們讀完並允許該字串。 ### 替換變數的方法 上面有提到如果 top 是一個變數 A,PDA 要找出一個可以替換掉 A 的規則替換掉他,至於所謂的替換,就是把 $A\rightarrow w$ 的右手邊寫入 stack。 並且要注意,要注意填入 stack 的順序,像是 $A\rightarrow BC$ 的話 B 就要最後放進 stack,實際放的時候如下: $$ \varepsilon, A \rightarrow C\\ \varepsilon, \varepsilon \rightarrow B\\ $$ 也就是說在 PDA 中會存在: - $\delta(q_i,\varepsilon,A)=(q_{i+1},C)$ - $\delta(q_i+1,\varepsilon,C)=(q_{i+2},B)$ 但是這樣寫的話有點麻煩,所以通常我們會用一個簡單的表示法簡寫成: $$ \delta(q_i,\varepsilon,A)=(q_{i+1},BC) $$ ## 詳細建構式證明 ![](https://drive.google.com/uc?id=1_PzItBr_mpfZwRr1jPfahgQpoFlhIfWG&export=download) 上面是我們根據 G 所模擬出的 PDA。 $$ P=(Q,\Sigma,\Gamma,\delta,q_{start},F)\\ Q = \{q_{start},q_{loop},q_{accept}\} $$ 起始狀態是 $q_{start}$,唯一的允許狀態是 $q_{accept}$。 轉換函數的部分有兩個步驟: - 首先先將$ 跟起始變數塞入 stack: $$ \delta(q_{start},\varepsilon,\varepsilon)=(q_{loop},S$) $$ - 接著是迴圈的部分 - 如果 top 是變數 A,並且有某個規則 $A→w$,$w$ 是由變數跟終端組成的字串 - $$\delta(q_{loop},\varepsilon,A)=(q_{loop},w)$$ - 如果 top 是終端 a - $$\delta(q_{loop},a,a)=(q_{loop},ε)$$ - 如果 top 是 $ - $$\delta(q_{loop},ε,$)=(q_{accpet},ε)$$ ## 例子 假如我們手上有一個文法 G: $$ S\rightarrow aTb\ |\ b\\\ T\rightarrow Ta\ |\ \varepsilon\\ $$ ![](https://drive.google.com/uc?id=1md8-ywR-3C_IvPcMoTZ8RMewsUCvjeaQ&export=download) 記住實際畫出來時,塞字串到 stack 的話還是要一個一個塞。 --- # 右到左 :::warning 如果一個 PDA 辨識某個語言,則該語言是「context free」。 ::: 先宣告變數: - $P$ 是我們的 PDA 這時候換成我們要用某個文法 G,來模擬某個 PDA 的行為;PDA 所生產的所有字串,這個文法 G 也都要可以產生出來。 ## 核心概念 我們會讓文法有這樣的一個變數: $$ A_{pq} $$ 這個變數可以產生出所有的字串,而這些字串可以使 $P$ 從狀態 $p$ 移動到狀態 $q$,並且「開始時跟結束時 stack 的狀態是一樣的」 > 也就是說進入 $p$ 以及之後,我們會在 stack 塞入一些內容,但是當我們到了 $q$ 的時候,那些塞入 stack 的內容又會被我們吐出來 有了 $A_{pq}$ 這個變數後,接著是定義其相關的規則,並且這些規則真的可以讓 $A_{pq}$ 產生出使 $P$ 從狀態 $p$ 移動到狀態 $q$,並且「開始時跟結束時 stack 的狀態是一樣的」的字串 ## 修改結構 為了讓推導過程比較輕鬆,我們將 PDA 轉換成另一種等價的形式: 1. PDA 只有一個允許狀態 $q_{accept}$ 2. 在進到允許狀態時 stack 會是空的 3. 每個 transition 要嘛是 push 一個字元,要嘛是 pop 一個字元,但是不可以都不做或同時做 第 1 點很簡單,就是把所有的允許狀態都拉一條空字串箭頭指向同一個新允許狀態 $q_{accept}$ 就好。 $$ ε, ε\rightarrow ε $$ 第 2 點也很簡單,就是多加幾個狀態把 stack 內容一個一個清掉 $$ ε, a\rightarrow ε $$ 然後不要忘記我們要新增自己的起始狀態 $q_{\text{my start}}$ 跟允許狀態 $q_{\text{my accept}}$來取代原本的起始狀態跟上面的 $q_{accept}$,因為要加入錢錢來判斷起始跟結束: $$ ε, ε\rightarrow $\\ ε, $\rightarrow ε\\ $$ 第 3 點其實也蠻容易的: - 如果有個 transition 為 $a,b\rightarrow c$ - 將他拆解成:$a,b\rightarrow ε$ 跟 $ε,ε\rightarrow c$ - 如果有個 transition 為 $a,ε\rightarrow ε$ - 將他拆解成:$a,ε\rightarrow b$ 跟 $ε,b\rightarrow c$ ## P 的運作過程 而對於這樣修改過的 P,我們來看看他運行的過程。對於任何 P 允許的字串 $x$,撇除 $ 符號不談: - P 計算 $x$ 的第一步一定是一個 push,因為我們有定義每一步(each transition) 都一定有 push 或 pop,而一開始 stack 是空的,所以一定是 push。 - 而最後 stack 會是空的,所以可以知道計算 $x$ 的最後一步一定是 pop。 此時我們可以遇到兩種情形 - 情形一:開頭 push 跟結尾 pop 的字元是同一個字元 - 因此可以知道 stack 在計算 $x$ 的過程中絕對不可能會變成空的 - 此時我們可以下 $A_{pq}\rightarrow aA_{rs}b$ 這樣的規則,這樣我們的文法就可以「稍為的」產生該字串的一部份 - 情形二:開頭 push 跟結尾 pop 的字元是不同一個字元 - 因此可以知道 stack 在計算 $x$ 的過程中有某些時候變成了空的 - 此時我們可以用 $A_{pq}\rightarrow A_{pr}A_{rq}$ 這樣的規則來模擬 P 的行為 - $r$ 是第一個 stack 變成空的的狀態 :::warning 所以這時候回顧為甚麼我們定義變數的方式如此特別,因為在修改過的 PDA 上,讀到一個字串時,**他的 stack 高度就像是一群山峰**,起始是空的結束時也是空的,中間可能有許多山谷,山谷可能不會到最底,也有可能到最底,詳細可以看下面的圖。 因此對於一個 PDA 允許的字串,我們可以把這些山峰切很多個水平刀,最底層的水平刀就是 $A_{q_0,q_{accept}}$,因為每個字串的 stack 起始是空的,結束時也是空的。 接著對於某個字串,如果他是情形一,則可以從 $A_{q_0,q_{accept}}$ 根據 PDA 的轉移函數,往上一點的高度再切一個水平刀,得到 $aA_{q_{i},q_{j}}b$,此時我們就把情形從原本的 $A_{q_0,q_{accept}}$ 遞迴到了子情形 $A_{q_{i},q_{j}}$。 而這種切法因為是根據原本的 PDA 的轉移函數所制定的,**所以只有這條規則將我們建立的文法跟實際要模擬的 PDA 建立起了連結。** 至於情形二,就是將原本的情形拆成兩個子情形。 ::: ## 建構 先宣告我們的 P 形式如下 $$ P=(Q,\Sigma,\Gamma,\delta,q_{0},F=\{q_{accept}\})\\ $$ 而我們建構的 $G$ 如下: $$ G=(V_G,\Sigma_G,R_G,S_G)\\ V_G=\{A_{pq}|p,q\in Q\}\\ S_G=A_{q0,q_{accept}} $$ 並且 $G$ 的規則 $R_G$ 如下,有三種情形: ### 情形一:連結情形 - 對於每個 $p,q,r,s\in Q,\ u\in \Gamma,\ a,b\in \Sigma_{ε}$,如果 - $\delta(p,a,ε)$ 得到的狀態中含有 $(r,u)$ - $\delta(s,b,u)$ 得到的狀態中含有 $(q,ε)$ - 則加入 $A_{pq}\rightarrow aA_{rs}b$ 這條規則 - 要小心,這裡加入的 $u$ 只會是「那個獨一無二的 $u$」,而不是跟 $u$ 一樣的字元就好 這種情形是我們所模擬的文法可以「跑起來」的根基,因為**只有這種規則跟實際 PDA 運行的過程有所連結**。 ### 情形二:拆解情形 - 對於每個 $p,q,r\in Q,\ u\in \Gamma,\ a,b\in \Sigma_{ε}$: - 加入 $A_{pq}\rightarrow A_{pr}A_{rq}$ 這條規則 這條規則代表說,由 $A_{pq}$ 產生的字串可以由 $A_{pr}$ 跟 $A_{rq}$ 兩種變數所產生的字串給「拼湊起來」。 **但是要注意,這裡為了方便性,是將全部可以拆解的可能的規則都加了進去,但實際上有些規則可能 PDA 根本不會發生**,也就是說這些加入的規則中會有一些冗員,詳細情形可以見[維基百科](https://en.wikipedia.org/wiki/Context-free_grammar#Decidable_problems)。 ### 情形三:末端情形 - 對所有的 $p\in Q$ - 加入 $A_{pp}\rightarrow ε$ 這個是屬於遞迴中的 terminal step,也就是何時停下來的那個「停下來」。對於某個字串,當他的 stack 山峰走到尖端的時候,令他讀 $a$ 後塞入 $u$ 走到尖端,讀到 $b$ 後吐出 $u$ 離開尖端; 此時令尖端左側、尖端,跟尖端右側的狀態分別為 $A$、$B$ 跟 $C$,由於這個是被 PDA 所允許,因此可以知道 PDA 具有 $\delta(A,a,\varepsilon)=(B,u)$ 跟 $\delta(B,b,u)=(C,\varepsilon)$ 這兩個轉移函數,而我們構建的文法就會因為這兩個規則而制定出 $A_{AC}\rightarrow aA_{BB}b$,這時就出現了末端情形 $A_{BB}$。 這也是為甚麼我們會讓 $A_{BB}\rightarrow ε$,因為跟實際字串運算有聯結的部分,我們都留給了情形一,因此末端情形可以安心的負責終結掉文法的 derivation 操作就好。 ## 山峰圖示 ![](https://drive.google.com/uc?id=1PObwhWzoxyKUAYa0Z6IlLFUuzbYIXj0y&export=download) 上面這張圖對應的是情形一,可以看到 $r$ 跟 $s$ 中間的連線就是上面所提及的第二刀。 ![](https://drive.google.com/uc?id=1dBMj345gNB_XsYvQj5BudR321mF2rHkq&export=download) 上面這張圖對應的是情形二,此時可以將這種字串由兩種變數產生的結果串聯起來。 # 證明 建構好 $G$ 的規則後,接著要證明「$A_{pq}$ 產生字串 $x$」若且唯若「$x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持起始跟結束時 stack 狀態一樣」 下面我們都會用歸納法來證明;為了方便說明,下面我會將「保持起始跟結束時 stack 狀態一樣」簡稱為「保持不變」。 ## 左到右 如果 $A_{pq}$ 產生字串 $x$,則 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持起始跟結束時 stack 狀態一樣。 我們的歸納法是以「derivation 的步數」,或者說「從 $A_{pq}$ derive 出 $x$ 的步數」作為歸納的骨牌。 - Base case:derivation 只有 1 步 - 「如果 $A_{pq}$ derive 出 $x$ 只有 1 步時,則 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持 stack 不變」。這是我們要推導的。 - 如果只花一步的替換就得到結果字串,那麼具有這種性質的變數只會是那一種,就是 $A_{pp}\rightarrow ε$ 的這種規則;所以如果當我們由 $A_{pp}$ 產生了 $ε$,我們要證明 $ε$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持 stack 不變 - 而 $ε$ 字串確實可以讓 $P$ 從 $p$ 走到 $p$ 並保持 stack 不變,因為你可以對任何狀態加入指向自己的空字串,並且不改變 stack 內容 - Induction step:已知「如果從 $A_{pq} \overset{*}{\Rightarrow} x$ 只有 k 步,則 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持 stack 不變」,接著要推導「如果從 $A_{pq} \overset{*}{\Rightarrow} x$ 只有 k+1 步,則 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持 stack 不變」 - 如果 $A_{pq} \overset{*}{\Rightarrow} x$ 只需要 k+1 步,第一步一定要嘛是 $A_{pq} \rightarrow aA_{rs}b$ 或 $A_{pq} \rightarrow A_{pr}A_{rq}$ - 如果第一步是 $A_{pq} \rightarrow aA_{rs}b$,令 $A_{rs}$ 產生的字串叫 $y$,則產生的字串可以寫為 $ayb$ - 因為 $A_{rs}$ 步數是 k,所以歸納法告訴我們 $P$ 從 $r$ 走到 $s$ 可以保持不變 - 而 $A_{pq} \rightarrow aA_{rs}b$ 根據上面建構時的定義,可以知道在那兩個轉移函數就是先塞入一個 $u$ 到 stack,然後接著以保持不變的方式產生出 y,最後又從 stack 吐出 $u$。 - 因此我們可以知道 $P$ 從 $p$ 走到 $q$ 可以保持不變。 - 如果第一步是 $A_{pq} \rightarrow A_{pr}A_{rq}$,令 $A_{pr}$ 產生的字串叫 $y$,$A_{rq}$ 產生的字串叫 $z$ - 因為 $A_{pr}$ 跟 $A_{rq}$ 的步數都小於等於 k,所以可以知道: - $P$ 從 $p$ 走到 $r$ 可以保持不變 - $P$ 從 $r$ 走到 $q$ 可以保持不變 - 所以 $P$ 可以從 $p$ 走到 $q$ 保持不變 - 這樣就完備了 Induction step,也就是說對所有的 derivation 次數,只要 $A_{pq}$ 產生字串 $x$,則 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持不變。 ## 右到左 如果 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持不變,則 $A_{pq}$ 產生字串 $x$ 我們的歸納法是以「Computation 的次數」,或者說「$P$ 計算 $x$ 所需的次數」作為歸納的骨牌。 - Base case:「如果只計算 0 次並保持不變,則 $A_{pq}$ 產生字串 $x$」 - 所以我們要從「計算 0 次並保持不變」證明出「$A_{pq}$ 產生字串 $x$」 - 如果只計算 0 次,那只會是 $\varepsilon$;並且起始跟結束狀態相同。 - 我們有定義 $A_{pp} \rightarrow \varepsilon$,所以 Base case 成立 - Induction step:已知「如果只計算 k 次並保持不變,則 $A_{pq}$ 產生字串 $x$」,接著要推導「如果只計算 k+1 次並保持不變,則 $A_{pq}$ 產生字串 $x$」 - 第一種情形,第一次 push 的跟最後一次 pop 的是相同的字元 - 令第一次輸入為 $a$,最後一次輸入為 $b$,中間字串為 $y$;$x=ayb$ - 令那個相同的字元為 $u$ - 因為 $x$ 讓 $P$ 從 $p$ 走到 $q$ 並保持不變,i.e. 我們具有 $\delta(p,a,ε)=(r,u)$ 跟 $\delta(s,b,u)=(q,ε)$,因此我們建構文法時會建構出 $A_{pq}\rightarrow aA_{rs}b$;i.e. - 因為 $y$ 需要的計算次數為 k 次,根據已知歸納結果,$A_{rs}$ 產生字串 $y$ - 所以我們可以知道 $A_{pq}$ 確實會產生出 $x$,$A\overset{*}{\Rightarrow}x$ - 第二種情形,第一次 push 的跟最後一次 pop 的是不相同的字元 - 此時令 $x=yz$,$x$ 到 $y$ 到 $z$ 分別經過 $p$、$r$ 跟 $q$三個狀態 - 因為 $y$ 跟 $z$ 計算次數都小於等於 k,根據已知歸納結果,$A_{pr}$ 跟 $A_{rq}$ 會產生出 $y$ 跟 $z$ - 而 $A_{pq}\rightarrow A_{pr}A_{rq}$ 這條規則在建構文法時有被加到規則裡,所以由上所述可以知道 $A_{pq}$ 確實會產生出 $x$,$A\overset{*}{\Rightarrow}x$ - 這樣就完備了 Induction Step,也就是說對於所有計算 $x$ 次數 k,只要 $x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持不變,則 $A_{pq}$ 產生字串 $x$。 ## 總結 這樣一來我們就成功證明 「$A_{pq}$ 產生字串 $x$」若且唯若「$x$ 可以讓 $P$ 從 $p$ 走到 $q$ 並保持起始跟結束時 stack 狀態一樣。 而所有 PDA 的字串都可以讓 $P$ 從 $q_0$ 走到 $q_{accept}$ 並保持起始跟結束時 stack 狀態一樣,因此可知$A_{q_{0},q_{accept}}$ 會產生所有 PDA 允許的字串