有限オートマトンを正規表現に変換する方法は?
On 2月 16, 2021 by admin正規表現を同じ言語を受け入れる(最小限の)NFAに変換するのは、標準のアルゴリズムを使用すると簡単です。 トンプソンのアルゴリズム。ただし、他の方向は面倒なようで、結果の式が乱雑になることがあります。
アルゴリズムとはNFAを同等の正規表現に変換するためにありますか?時間計算量または結果サイズに関する利点はありますか?
これは参照質問であると思われます。メソッドの一般的な説明と、自明ではない例。
コメント
- 同様の質問に注意してください。 cstheory.SE これはおそらく私たちの聴衆には適していません。
- すべての回答は正式な手法を使用してDFAからREを記述しています。分析による私の手法は比較的簡単で客観的であると私は答えています:この決定性有限オートマトンの言語は何ですか?いつか役立つと思います。もちろん、私自身が正式な方法を使用することもあります(アーデンの定理) REを書くことは質問は次の例のように複雑です: DFAの正規表現を作成する方法
回答
有限オートマトンから正規表現への変換を行う方法はいくつかあります。ここでは、学校で通常教えられている非常に視覚的なものについて説明します。私はそれが実際に最も使用されていると信じています。ただし、アルゴリズムを作成することはあまり良い考えではありません。
状態の削除方法
このアルゴリズムはオートマトンのグラフを処理するためのものであり、必要なためアルゴリズムにはあまり適していません。 …状態の削除などのグラフプリミティブ。高レベルのプリミティブを使用して説明します。
重要なアイデア
このアイデアは、エッジの正規表現を検討し、エッジラベルの一貫性を保ちながら中間状態を削除することです。
主なパターンは次の図に示されています。 1つ目は、正規表現$ e、f、g、h、i $である$ p、q、r $の間にラベルがあり、$ q $を削除します。
削除すると、$ e、f、g、h、i $を一緒に構成します($ p $と$ r $の間の他のエッジは保持しますが、これは表示されません)これについて):
例
ラファエルの答え:
$ q_2 $を連続して削除します:
、次に$ q_3 $:
それでも、$ q_1 $から$ q_1 $までの式にスターを適用する必要があります。この場合、最終的な状態は次のようになります。また、初期なので、実際には星を追加する必要があります:
$$(ab +(b + aa)(ba)^ *(a + bb))^ * $$
アルゴリズム
L[i,j]
は、$ q_i $から$ q_j $までの言語の正規表現です。最初に、すべてのmulを削除します。 ti-edges:
for i = 1 to n: for j = 1 to n: if i == j then: L[i,j] := ε else: L[i,j] := ∅ for a in Σ: if trans(i, a, j): L[i,j] := L[i,j] + a
これで、状態が削除されました。状態$ q_k $を削除したいとします。
remove(k): for i = 1 to n: for j = 1 to n: L[i,i] += L[i,k] . star(L[k,k]) . L[k,i] L[j,j] += L[j,k] . star(L[k,k]) . L[k,j] L[i,j] += L[i,k] . star(L[k,k]) . L[k,j] L[j,i] += L[j,k] . star(L[k,k]) . L[k,i]
紙の鉛筆とアルゴリズムの両方を使用して、
、e.ε=e
、∅+e=e
、∅.e=∅
(作成者$∅$でない場合、またはセルフループの場合は$ε$でさえもエッジを書き込まず、$ q_i $と$ q_k $または$ q_j $と$の間に遷移がない場合は無視します。 q_k $)
では、remove(k)
の使用方法は?最終状態または初期状態を軽く削除しないでください。そうしないと、言語の一部が失われます。
for i = 1 to n: if not(final(i)) and not(initial(i)): remove(i)
最終状態$ q_f $が1つと1つしかない場合初期状態$ q_s $の場合、最終式は次のようになります。
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
複数の最終状態(または初期状態)がある場合、マージする簡単な方法はありません。これらは、推移閉包法を適用する以外のものです。通常、これは手作業では問題になりませんが、アルゴリズムを作成するときには厄介です。はるかに簡単な回避策は、すべてのペア$(s、f)$を列挙し、(すでに状態が削除された)グラフでアルゴリズムを実行して、$ s $が唯一の初期状態であると仮定してすべての式$ e_ {s、f} $を取得することです。そして、$ f $が唯一の最終状態であり、すべての$ e_ {s、f} $の結合を実行します。
これと、これが最初の方法よりも動的に言語を変更しているという事実により、プログラミング時にエラーが発生しやすくなります。他の方法を使用することをお勧めします。
短所
このアルゴリズムには、削除するノードの選択、最後の最終状態の数など、多くの場合があります。 、最終状態も初期状態になる可能性があるという事実など。
アルゴリズムが記述されたので、これは推移閉包法によく似ていることに注意してください。使用法のコンテキストのみが異なります。アルゴリズムの実装はお勧めしませんが、この方法を使用して手動で実装することをお勧めします。
コメント
- この例では、ノード” 2 “、エッジがありません-ノードAのループエッジ(ab)。
- @Kabamaru:修正されました。しかし今では、3番目の画像の$ \ varepsilon $も
ab
である必要があり、同様に最終的な正規表現でもあるはずです。 - アルゴリズムを作成できます。新しい初期$ q ^ + $と新しい最終状態$ q ^-$を追加し、これらを$ \ varepsilon $ -edgesで元の初期状態と最終状態に接続することにより、任意の数の初期状態と最終状態に対して機能します。次に、すべての元の状態を削除します。次に、式は$ q ^ + $から$ q _- $までの残りの1つのエッジで見つかります。 $ q ^ + $または$ q _- $でループが発生するのは、これらの状態に応答がないためです。発信エッジ。または、厳密な場合は、空のセットを表すラベルが付けられます。
- 2番目の例にはまだ問題があります。簡略化する前に、オートマトンは” baを受け入れます。 “、(1、3、1)ですが、簡略化した後は’ t。
回答
メソッド
私が見た中で最も優れたメソッドは、オートマトンを(正規)言語の連立方程式として表現するメソッドです。解決される。他の方法よりも簡潔な式が得られるように見えるので、特に便利です。
$ A =(Q、\ Sigma、\ delta、q_0、F)$を$ \ varepsilon $なしのNFAとします-遷移。すべての状態$ q_i $について、方程式を作成します
$ \ qquad \ displaystyle Q_i = \ bigcup \ limits_ {q_i \ overset {a} {\ to} q_j} aQ_j \ cup \ begin {cases} \ {\ varepsilon \} &、\ q_i \ in F \\ \ emptyset &、\ text {else} \ end {cases} $
ここで、$ F $は最終状態のセットであり、$ q_i \ overset {a} {\ to} q_j $は、$ a $でラベル付けされた$ q_i $から$ q_j $への遷移があることを意味します。 。 $ \ cup $を$ + $または$ \ mid $(正規表現の定義に応じて)と読むと、これが正規表現の方程式であることがわかります。
システムを解くには、結合法則が必要です。 $ \ cup $と$ \ cdot $(文字列の連結)の分配法則、$ \ cup $と Ardenのレンマ¹の可換性¹:
$ L、U、V \ subseteq \ Sigma ^ * $正規言語を$ \ varepsilon \ notin U $で使用します。次に、
$ \ qquad \ displaystyle L = UL \ cup V \ quad \ Longleftrightarrow \ quad L = U ^ * V $
解決策は正規表現のセットです$ Q_i $、すべての状態$ q_i $に1つ。$ Q_i $は、$ q_i $で開始したときに$ A $が受け入れることができる単語を正確に記述します。したがって、$ Q_0 $($ q_0 $が初期状態の場合)は必要な式。
例
わかりやすくするために、シングルトンセットを要素($ a = \ {a \} $)で表します。例はGeorgZetzscheによるものです。
tを検討してください彼のNFA:
[ソース]
対応する方程式系は次のとおりです。
$ \ qquad \ begin {align} Q_0 & = aQ_1 \ cup bQ_2 \ cup \ varepsilon \\ Q_1 & = bQ_0 \ cup aQ_2 \\ Q_2 & = aQ_0 \ cup bQ_1 \ end {align} $
次に、3番目の方程式を2番目の方程式に代入します。
$ \ qquad \ begin {align} Q_1 & = bQ_0 \ cup a(aQ_0 \ cup bQ_1)\\ & = abQ_1 \ cup(b \ cup aa)Q_0 \\ & =(ab)^ *( b \ cup aa)Q_0 \ end {align} $
最後のステップでは、Ardenの補題を$ L = Q_1 $、$ U = ab $、$ V =(b \ cup aa)\ cdot Q_0 $。 3つの言語はすべて通常であり、$ \ varepsilon \ notin U = \ {ab \} $であるため、見出語を適用できることに注意してください。ここで、この結果を最初の方程式に代入します。
$ \ qquad \ begin {align} Q_0 & = a(ab)^ *(b \ cup aa )Q_0 \ cup baQ_0 \ cup bb(ab)^ *(b \ cup aa)Q_0 \ cup \ varepsilon \\ & =((a \ cup bb)(ab)^ *(b \ cup aa)\ cup ba)Q_0 \ cup \ varepsilon \\ & =((a \ cup bb)(ab)^ *(b \ cup aa)\ cup ba)^ * \ qquad \ text {(by Arden “s Lemma)} \ end {align} $
したがって、上記のオートマトンで受け入れられる言語の正規表現、つまり
$ \ qquad \ displaystyle((a + bb)(ab)^ *(b + aa)+ ba)^ *。$
非常に簡潔であることに注意してください(他の方法の結果)が一意に決定されていません。異なる操作シーケンスで方程式系を解くと、他の-同等の!-式になります。
- アーデンの証明のために」 ■レンマ、こちらを参照してください。
コメント
- 内容このアルゴリズムの時間計算量は何ですか?生成される式のサイズに制限はありますか?
- @jmite:わかりません。 ‘これを実装しようとは思わない’(この点では他の方法の方が実行可能と思われる)が、次のように使用するペンと紙の方法。
- ここ’ saこのアルゴリズムのProlog実装: github.com / wvxvw / intro-to-automata-theory / blob / master / automata / … ですが、その
maybe_union/2
述語はよりきちんとした正規表現を作成するためのより多くの作業(特に、共通のプレフィックスを削除するwrt)。この方法を確認するもう1つの方法は、正規表現から右線形文法への変換として理解することです。ここでは、Prologのような統一またはMLのようなパターンマッチングを備えた言語が非常に優れたトランスデューサーになるため、’はペンと紙のアルゴリズムだけではありません:) - 質問は1つだけです。最初の式のεは、Qoが開始状態であるためですか、それとも’が最終状態であるためですか。 2つの最終状態が適用される場合も同じ方法ですか?
- @PAOK上記の$ Q_i $の定義を確認してください(行)。 $ q_0 $が最終状態であるため、’です。
回答
Brzozowski代数的方法
これは、 Raphaelの回答で説明されている方法と同じですが、体系的なアルゴリズム、そして実際にはアルゴリズムのビュー。どこから始めればよいかがわかれば、実装は簡単で自然であることがわかります。また、何らかの理由ですべてのオートマトンを描画することが実用的でない場合は、手作業で簡単に実行できる場合があります。
アルゴリズムを作成するときは、方程式を常に線形にする必要があることを覚えておく必要があります。これにより、方程式を適切に抽象的に表現できます。これは、手作業で解くときに忘れることができます。
アルゴリズムのアイデア
ラファエルの回答でうまく行われているため、アルゴリズムの仕組みについては説明しません。代わりに、あまり多くの余分なアルゴリズムを実行せずに方程式を解くべき順序に焦点を当てます。計算または追加のケース。
アーデンのルールの独創的なソリューション$ X = A ^ * B $から言語方程式へ$ X =AX∪B$オートマトンは次の形式の方程式のセットと見なすことができます。
$$ X_i = B_i + A_ {i、1} X_1 +…+ A_ {i、n} X_n $$
配列$ A_ {i、j} $と$ B_ {i、j} $を適宜更新することにより、$ n $の誘導によってこれを解決できます。ステップ$ n $には、次のものがあります。
$$ X_n = B_n + A_ {n、1} X_1 +…+ A_ {n、n} X_n $$
およびアーデンのルールは次のようになります。
$$ X_n = A_ {n、n} ^ *(B_n + A_ {n、1} X_1 +…+ A_ {n、n-1} X_ {n -1})$$
そして$ B “_n = A_ {n、n} ^ * B_n $と$ A” _ {n、i} = A_ {n、n} ^ *を設定するA_ {n、i} $取得:
$$ X_n = B “_n + A” _ {n、1} X_1 +…+ A “_ {n、n-1} X_ {n -1} $$
次に、$ i、j < n $:
$$ B “_i = B_i + A_ {i、n} B” _n $$ $$ A “_ {i、j} = A_ {i、j} + A_ {i、n} A “_ {n、j} $$
$ n = 1 $のときに$ X_n $を解くと、次のような方程式が得られます。
$$ X_1 = B” _1 $$
$ A “_ {1、i} $なし。このようにして、正規表現を取得しました。
アルゴリズム
これにより、アルゴリズムを構築できます。上記の誘導と同じ規則を使用するために、初期状態は$ q_1 $であり、状態の数は$ m $であると言います。まず、$ B $を埋めるための初期化:
for i = 1 to m: if final(i): B[i] := ε else: B[i] := ∅
と$ A $:
for i = 1 to m: for j = 1 to m: for a in Σ: if trans(i, a, j): A[i,j] := a else: A[i,j] := ∅
次に解く:
for n = m decreasing to 1: B[n] := star(A[n,n]) . B[n] for j = 1 to n: A[n,j] := star(A[n,n]) . A[n,j]; for i = 1 to n: B[i] += A[i,n] . B[n] for j = 1 to n: A[i,j] += A[i,n] . A[n,j]
最終的な式は次のようになります:
e := B[1]
実装
アルゴリズムには象徴的すぎるように見える連立方程式のように見える場合でも、これは実装に適しています。 これは、Ocamlでのこのアルゴリズムの実装です(リンク切れ)。関数brzozowski
を除いて、すべてが印刷されるか、Raphaelの例に使用されることに注意してください。正規表現を単純化する驚くほど効率的な関数。
コメント
- リンクが切れています…
- Javascriptでの実装: github.com/devongovett/regexgen/blob/master/src/regex.js
- このすばらしい説明に感謝します。正しく理解できれば、初期化疑似コードは、与えられたiとjに対して、(i、a、j)が遷移であるようなaが最大で1つあることを前提としています。これは、この遷移に、ラベルが文字オートマトンでiからjに遷移するΣですが、Σの表記aは実際には文字ではないため、少し奇妙です。文字ごとに移動すると、iからjにいくつか遷移する可能性があり、ループ本体でラベルの結合を行います。
回答
推移閉包メソッド
このメソッドはフォームに簡単に記述できますアルゴリズムのですが、非常に大きな正規表現を生成し、手作業で行うと実用的ではありません。これは主に、これが体系的すぎるためです。ただし、これはアルゴリズムにとって優れた単純なソリューションです。
重要なアイデア
$ R ^ k_ {i、j} $は、$から始まる文字列の正規表現を表します。状態$ \ {q_1、…、q_k \} $を使用してq_i $から$ q_j $へ。 $ n $をオートマトンの状態の数とします。
中間状態$ q_kのない$ q_i $から$ q_j $までの正規表現$ R_ {i、j} $がすでにわかっているとします。 $(四肢を除く)、すべての$ i、j $。次に、別の状態を追加すると、新しい正規表現$ R “_ {i、j} $にどのように影響するかを推測できます。$ q_k $に直接遷移する場合にのみ変化し、次のように表現できます。
$$ R “_ {i、j} = R_ {i、j} + R_ {i、k}。 R_ {k、k} ^ *。 R_ {k、j} $$
($ R $は$ R ^ {k-1} $で、$ R “$は$ R ^ k $です。)
例
ラファエルの回答と同じ例を使用します。最初は、直接遷移のみを使用できます。
これが最初のステップです(ラベル$ a $の自己ループは最初の$ε$を$(ε+ a)に変換することに注意してください) $。
$$ R ^ 0 = \ begin {bmatrix}ε& a & b \\ b &ε& a \\ a & b &ε\ end {bmatrix} $$
2番目のステップでは、$ q_0 $を使用できます($ R ^ 0 $はすでに使用されているため、$ q_1 $に名前が変更されています。上記の目的)$ R ^ 1 $がどのように機能するかを見ていきます。
$ q_2 $から$ q_2 $へ:$ R ^ 1_ {2,2} = R ^ 0_ {2,2} + R ^ 0_ {2,1} {R ^ 0_ {1,1}} ^ * R ^ 0_ {1,2} =ε+bε^ * a =ε+ ba $。
それはなぜですか?中間状態として$ q_1 $のみを使用して$ q_2 $から$ q_2 $に移動するには、ここ($ε$)にとどまるか、$ q_1 $($ a $)に移動して、そこでループする必要があります。 ($ε^ * $)そして戻ってくる($ b $)。
$$ R ^ 1 = \ begin {bmatrix}ε& a & b \\ b &ε+ ba & a + bb \\ a & b + aa &ε+ ab \ end {bmatrix} $$
$ R ^ 2 $と$ R ^ 3 $のように計算することもでき、$ R ^ 3_ {1,1 $ 1 $は最初と最後の両方であるため、} $は最終的な式を提供します。ここでは、式の多くの簡略化が行われていることに注意してください。それ以外の場合、$ R ^ 0 $の最初の$ a $は$(∅+ a)$になり、$ R ^ 1 $の最初の$ a $は$((∅+ a)+ε(ε)^ * aになります)$。
アルゴリズム
初期化:
for i = 1 to n: for j = 1 to n: if i == j: R[i,j,0] := ε else: R[i,j,0] := ∅ for a in Σ: if trans(i, a, j): R[i,j,0] := R[i,j,0] + a
推移閉包:
for k = 1 to n: for i = 1 to n: for j = 1 to n: R[i,j,k] := R[i,j,k-1] + R[i,k,k-1] . star(R[k,k,k-1]) . R(k,j,k-1)
次に、最終的な式は次のようになります($ q_s $が初期状態であると仮定):
e := ∅ for i = 1 to n: if final(i): e := e + R[s,i,n]
しかし、想像できます醜い正規表現を生成します。 $ aa $と同じ言語を表す$(∅)^ * +(a +(∅)^ *)(ε)^ *(a +∅)$のようなものを実際に期待できます。実際には、正規表現を単純化すると便利です。
コメントを残す