機能と制限に基づいて、DFAエンジンとNFAエンジンの違いについて非技術的説明を探しています。
確定的有限オートマトン(DFA)と非確定的有限オートマトン(NFA)には、まったく同じ機能と制限があります。唯一の違いは表記上の便宜です。
有限オートマトンは、状態を持ち、入力を読み取るプロセッサであり、各入力文字はそれを別の状態に設定する可能性があります。たとえば、「2つのCを続けて読み取る」または「単語を開始しています」などの状態が考えられます。これらは通常、ソースコードを字句スキャンしてトークンに変換するなど、パターンを見つけるためのテキストのクイックスキャンに使用されます。
確定的有限オートマトンは一度に1つの状態にあり、実装可能です。非決定的有限オートマトンは、一度に複数の状態になる可能性があります。たとえば、識別子が数字で始まる言語では、「数値を読み取る」状態と「識別子を読み取る」状態があり、 NFAは、「123」で始まる何かを読み取るときに両方に同時に存在する可能性があります。実際にどの状態が当てはまるかは、それが単語の終わりの前に数値ではない何かに遭遇したかどうかによって異なります。
これで、「数値または識別子の読み取り」を状態自体として表現できるようになり、突然NFAは必要なくなりました。 NFA内の状態の組み合わせを状態自体として表すと、NFAよりもはるかに多くの状態を持つDFAが得られますが、同じことを行います。
どちらが読みやすく、書きやすく、扱いやすいかが問題です。 DFA自体は理解しやすいですが、NFAは一般的に小さくなります。
Microsoftからの非技術的な答えは次のとおりです。
DFAエンジンはバックトラックを必要としないため、線形時間で実行されます(したがって、同じ文字を2回テストすることはありません)。また、可能な限り最長の文字列との一致を保証できます。ただし、DFAエンジンには有限の状態しか含まれていないため、パターンを後方参照と照合することはできません。また、明示的な展開を構築しないため、部分式をキャプチャできません。
従来のNFAエンジンは、いわゆる「貪欲な」一致バックトラッキングアルゴリズムを実行し、特定の順序で正規表現のすべての可能な拡張をテストし、最初の一致を受け入れます。従来のNFAは、一致を成功させるために正規表現の特定の拡張を構築するため、部分式の一致と一致する後方参照をキャプチャできます。ただし、従来のNFAはバックトラックするため、状態が異なるパスを経由して到達した場合、まったく同じ状態に何度もアクセスできます。その結果、最悪の場合、指数関数的にゆっくり実行されます。従来のNFAは最初に見つかった一致を受け入れるため、他の(おそらく長い)一致を検出されないままにすることもできます。
POSIX NFAエンジンは従来のNFAエンジンに似ていますが、可能な限り最長の一致が見つかるまで、バックトラックを続けます。その結果、POSIX NFAエンジンは従来のNFAエンジンよりも低速であり、POSIX NFAを使用する場合、バックトラッキング検索の順序を変更して、長い一致よりも短い一致を優先することはできません。
従来のNFAエンジンは、DFAまたはPOSIX NFAエンジンよりも表現力が高いため、プログラマーに好まれています。最悪の場合、実行速度は遅くなりますが、あいまいさを減らし、バックトラックを制限するパターンを使用して、線形時間または多項式時間で一致を見つけるように誘導できます。
[http://msdn.Microsoft.com/en-us/library/0yzc2yb0.aspx]
Jeffrey Friedlの本 Mastering Regular Expressions から言い換えられた、単純な非技術的な説明。
[〜#〜]警告[〜#〜]:
この本は一般に「正規表現の聖書」と見なされていますが、DFAとNFAの間でここで行われた区別が実際に正しいかどうかについては、いくつかの論争があるようです。私はコンピュータサイエンティストではありません。決定論的であるかどうかにかかわらず、「正規の」表現とは何かを裏付ける理論のほとんどは理解していません。論争が始まった後、私はこのためこの回答を削除しましたが、それ以来、他の回答へのコメントで参照されています。私はこれについてさらに議論することに非常に興味があります-フリードルが本当に間違っているのでしょうか?または、Friedlを間違えましたか(ただし、昨日の夜にその章を読み直しました。覚えていたとおりです...)?
編集:フリーデルと私は本当に間違っているようです。以下のイーモンの素晴らしいコメントをチェックしてください。
元の答え:
DFAエンジンは、入力文字列を1文字ずつステップ実行し、可能なすべての方法を試行(および記憶)します。この時点で正規表現が一致する可能性があります。文字列の最後に到達すると、成功を宣言します。
文字列AAB
と正規表現A*AB
を想像してみてください。次に、文字列を1文字ずつ進めていきます。
A
:
A*
で照合できます。A*
(ゼロの繰り返しが許可されます)を無視し、正規表現で2番目のA
を使用することにより、一致させることができます。A
:
A*
を展開すると照合できます。B
に一致しません。 2番目のブランチは失敗します。だが:A*
を展開せず、代わりに2番目のA
を使用することで一致させることができます。B
:
A*
を展開するか、正規表現で次のトークンA
に移動しても一致しません。最初のブランチは失敗します。DFAエンジンが文字列をバックトラックすることはありません。
NFAエンジンは、regexトークンをトークンごとにステップ実行し、文字列に対して可能なすべての置換を試行します。必要であれば。正規表現の最後に到達すると、成功を宣言します。
以前と同じ文字列と同じ正規表現を想像してください。トークンごとに正規表現トークンをステップ実行します。
A*
:AA
に一致します。バックトラック位置0(文字列の先頭)と1を思い出してください。A
:一致しません。しかし、私たちは戻って再試行できるバックトラック位置を持っています。正規表現エンジンは1文字戻ります。 A
が一致します。B
:一致。正規表現の終わりに達しました(1つのバックトラック位置に余裕があります)。やったー!名前が示すように、NFAとDFAはどちらも有限オートマトンです。
どちらも、開始状態、成功(または「受け入れ」)状態(または一連の成功状態)、および遷移をリストする状態テーブルとして表すことができます。
DFAの状態テーブルでは、各<state₀, input>
キーは1つだけに移行しますstate₁
。
NFAの状態テーブルでは、各<state₀, input>
はsetの状態に遷移します。
DFAを取得するときは、DFAを開始状態、一連の入力シンボルにリセットします。DFAの終了状態と、成功状態かどうかが正確にわかります。
ただし、NFAを取得すると、入力シンボルごとに、可能な結果状態のセットが検索され、(理論的には)ランダムに、非決定的に、それらの1つが選択されます。その入力文字列の成功状態の1つにつながるランダムな選択のセットが存在する場合、DFAはその文字列で成功したと言います。つまり、魔法のように常に正しいものを選択するふりをすることが期待されます。
計算における初期の疑問の1つは、その魔法のためにNFAがDFAよりも強力であるかどうかでした。答えはnoであることが判明しました。NFAは同等のDFAに変換できるためです。 それらの機能と制限は互いにまったく同じです。
Jan GoyvaertsによるRegular Expressions、The Complete Tutorialで与えられた説明が最も使いやすいと思います。このPDFの7ページを参照してください。
https://www.princeton.edu/~mlovett/reference/Regular-Expressions.pdf
7ページで行われた他のポイントの中で正規表現エンジンには、テキスト指向エンジンと正規表現指向エンジンの2種類があります。 Jeffrey Friedlは、それぞれDFAおよびNFAエンジンと呼びます。...遅延量指定子や逆参照などの非常に便利な機能は、正規表現向けエンジンでのみ実装できます。