DFA против двигателей NFA: В чем разница в их возможностях и ограничениях?

Я ищу нетехническое объяснение разницы между двигателями DFA и NFA на основе их возможностей и ограничений.

Ответ 1

Детерминированные конечные автоматы (DFA) и недетерминированные конечные автоматы (NFA) имеют точно такие же возможности и ограничения. Единственное отличие - это нотация.

Конечный автомат - это процессор, который имеет состояния и считывает ввод, каждый входной символ потенциально может установить его в другое состояние. Например, состояние может быть "просто прочитано два Cs в строке" или "Я начинаю слово". Они обычно используются для быстрого сканирования текста, чтобы найти шаблоны, такие как лексическое сканирование исходного кода, чтобы превратить его в токены.

Детерминированный конечный автомат находится в одном состоянии за раз, что реализуемо. Недетерминированный конечный автомат может быть более чем в одном состоянии за раз: например, на языке, где идентификаторы могут начинаться с цифры, может существовать состояние "чтение числа" и другое состояние "чтение идентификатора", а также NFA может быть одновременно и при чтении чего-то, начиная с "123". Какое состояние действительно применяется, будет зависеть от того, столкнулось ли оно с чем-то не численным до конца слова.

Теперь мы можем выразить "чтение номера или идентификатора" как самого состояния, и внезапно нам не нужен NFA. Если мы выражаем комбинации состояний в NFA как сами по себе, у нас есть DFA с гораздо большим количеством состояний, чем NFA, но что делает то же самое.

Это вопрос, о котором легче читать или писать или разбираться. DFA легче понять сами по себе, но NFA обычно меньше.

Ответ 2

Здесь нетехнический ответ от Microsoft:

Двигатели DFA работают в линейном режиме, потому что они не требуют возврата (и поэтому они никогда не тестируют один и тот же символ дважды). Они также могут гарантировать соответствие самой длинной строки. Однако, поскольку механизм 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]

Ответ 3

Простое, нетехническое объяснение, перефразированное из книги Джеффри Фридля Освоение регулярных выражений.

CAVEAT

В то время как эта книга обычно рассматривается как "библия регулярных выражений", возникают некоторые разногласия относительно того, действительно ли различие между DFA и NFA является правильным. Я не компьютерный ученый, и я не понимаю большую часть теории того, что на самом деле является "регулярным" выражением, детерминированным или нет. После того, как начались противоречия, я удалил этот ответ из-за этого, но с тех пор он упоминается в комментариях к другим ответам. Мне было бы очень интересно обсудить это дальше - может быть, Фридль действительно ошибается? Или я неправильно понял Фридла (но вчера вечером я перечитывал эту главу, и это, как я помнил...)?

Изменить: Похоже, что мы с Фридлом действительно ошибаемся. Пожалуйста, ознакомьтесь с замечательными комментариями Eamon ниже.


Оригинальный ответ:

A движок DFA выполняет ввод символа входной строки по символу и пытается (и запоминает) все возможные способы, с помощью которых регулярное выражение могло бы совпадать в этой точке. Если он достигает конца строки, он объявляет успех.

Представьте строку AAB и регулярное выражение A*AB. Теперь мы переходим через нашу строчную букву по букве.

  • A:

    • Первая ветвь: может быть сопоставлена ​​ A*.
    • Вторая ветвь: может быть сопоставлена ​​игнорированием A* (нулевые повторения разрешены) и использование второго A в регулярном выражении.
  • A:

    • Первая ветвь: может быть сопоставлена ​​расширением A*.
    • Вторая ветвь: не может быть сопоставлена ​​ B. Вторая ветвь не работает. Но:
    • Третья ветвь: может быть сопоставлена, не расширяя A* и вместо этого используя второй A.
  • B:

    • Первая ветвь: не может быть сопоставлена ​​расширением A* или перемещением в регулярном выражении на следующий токен A. Не удалось выполнить первую ветку.
    • Третья ветвь: может быть сопоставлена. Ура!

Двигатель DFA никогда не возвращается в строку.


Механизм NFA проходит через токен regex маркером и пытается все возможные перестановки в строке, при необходимости возвращаясь назад. Если он достигнет конца регулярного выражения, он объявит успех.

Представьте себе ту же строку и то же регулярное выражение, что и раньше. Теперь мы переходим через токен регулярного выражения через токен:

  • A*: совпадение AA. Помните позиции возврата 0 (начало строки) и 1.
  • A: не соответствует. Но у нас есть обратная позиция, с которой мы можем вернуться и попробовать снова. Механизм регулярных выражений отбрасывает один символ. Теперь соответствует A.
  • B: Соответствует. Конец регулярного выражения достиг (с одним возвратным положением, чтобы сэкономить). Ура!

Ответ 4

Оба NFA и DFA являются конечными автоматами, как говорят их имена.

Оба могут быть представлены как начальное состояние, состояние успеха (или "принятие" ) (или множество состояний успеха) и переходы из таблицы состояний.

В таблице состояний DFA каждая клавиша <state₀, input> будет переходить на один и только один state₁.

В таблице состояний NFA каждый <state₀, input> будет переходить в состояние set.

Когда вы берете DFA, reset, он должен начать состояние, последовательность входных символов, и вы точно знаете, в каком конце оно находится, и является ли это успешным состоянием или нет.

Однако, когда вы берете NFA, для каждого входного символа будет отображаться множество возможных состояний результата и (теоретически) случайным образом, недетерминистически, выбрать один из них. Если существует набор случайных выборок, которые приводят к одному из состояний успеха для этой входной строки, то, как говорят, DFA преуспевает для этой строки. Другими словами, вы должны притворяться, что он магически всегда выбирает правильный.

Один из ранних вопросов при вычислении заключался в том, были ли NFA более мощными, чем DFA, из-за этой магии, и ответ оказался no, поскольку любой NFA мог быть переведен в эквивалентный DFA. Их возможности и ограничения точно так же, как и другие.

Ответ 5

Я нахожу объяснение, данное в "Регулярных выражениях", "Полное учебное пособие Яна Гойвартса", которое может быть наиболее полезным. См. Стр. 7 этого PDF:

https://www.princeton.edu/~mlovett/reference/Regular-Expressions.pdf

Среди других пунктов, сделанных на стр. 7, существуют два типа движков регулярного выражения: текстовые движки и ориентированные на регулярные выражения. Джеффри Фридл называет их двигателями DFA и NFA, соответственно.... некоторые очень полезные функции, такие как ленивые кванторы и обратные ссылки, могут быть реализованы только в программах, ориентированных на регулярные выражения.