Как преобразовать конечные автоматы в регулярные выражения?

115

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

Какие существуют алгоритмы для преобразования NFA в эквивалентные регулярные выражения? Есть ли преимущества в отношении сложности времени или размера результата?

Это должен быть справочный вопрос. Пожалуйста, включите общее описание вашего метода, а также нетривиальный пример.

Рафаэль
источник
2
Обратите внимание на аналогичный вопрос на cstheory.SE, который, вероятно, не подходит для нашей аудитории.
Рафаэль
все ответы используют формальную технику для написания RE из DFA. Я полагаю, что моя методика анализа сравнительно проста и объективна, что я демонстрирую в своем ответе: « Каков язык этих детерминированных конечных автоматов? Я чувствую, что это будет полезно когда-нибудь. Да, конечно, иногда я сам использую формальный метод (теорему Ардена) для написания RE, это сложный вопрос, как
показано

Ответы:

94

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

Метод удаления состояния

Этот алгоритм предназначен для обработки графа автомата и поэтому не очень подходит для алгоритмов, так как ему нужны графовые примитивы, такие как ... удаление состояний. Я опишу это с помощью примитивов более высокого уровня.

Ключевая идея

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

Основную закономерность можно увидеть на следующих рисунках. Первый имеет метки между которые являются регулярными выражениями и мы хотим удалить .e , f , g , h , i qp,q,re,f,g,h,iq

PQR автомат

После удаления мы составляем вместе (сохраняя остальные ребра между и но это не отображается на этом):p re,f,g,h,ipr

введите описание изображения здесь

пример

Используя тот же пример, что и в ответе Рафаэля :

1-2-3 автомат

мы последовательно удаляем :q2

1-3 автомат

а затем :q3

1 автомат

тогда мы все еще должны применить звезду к выражению от до . В этом случае конечное состояние также является начальным, поэтому нам просто нужно добавить звездочку:q 1q1q1

(ab+(b+aa)(ba)(a+bb))

Алгоритм

L[i,j]это регулярное выражение языка от до . Сначала мы удалим все мульти-ребра:q jqiqj

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

Теперь состояние снятия. Предположим, мы хотим удалить состояние :qk

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]

Обратите внимание , что оба с карандашом бумаги и с помощью алгоритма вы должны упростить выражения , как star(ε)=ε, e.ε=e, ∅+e=e, ∅.e=∅(Под рукой вы просто не написать край , когда он не , или даже для самостоятельного цикла и вы игнорируете , когда есть нет перехода между и или и )εq k q j q kqiqkqjqk

Теперь, как использовать remove(k)? Вы не должны легко удалять окончательные или начальные состояния, иначе вы пропустите части языка.

for i = 1 to n:
  if not(final(i)) and not(initial(i)):
    remove(i)

Если у вас есть только одно конечное состояние и одно начальное состояние тогда конечное выражение будет:q sqfqs

e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])

Если у вас есть несколько конечных состояний (или даже начальных состояний), то нет простого способа их объединения, кроме применения метода транзитивного замыкания. Обычно это не проблема вручную, но это неудобно при написании алгоритма. Гораздо более простой обходной путь - перечислить все пары и запустить алгоритм на графе (уже удаленном из состояния), чтобы получить все выражения предполагая, что является единственным начальным состоянием, а является единственным конечным состояние, то делает объединение всех .е с , е ы е д ы , е(s,f)es,fsfes,f

Это и тот факт, что это модифицирует языки более динамично, чем первый метод, делает его более подверженным ошибкам при программировании. Я предлагаю использовать любой другой метод.

Cons

В этом алгоритме много случаев, например, для выбора узла, который мы должны удалить, количества конечных состояний в конце, факта, что конечное состояние может быть начальным, и т. Д.

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

jmad
источник
1
В примере 2-го изображения после удаления узла «2» отсутствует ребро - ребро петли (ab) в узле A.
Panos Kal.
@Kabamaru: исправлено. Но теперь я думаю, что в 3-м изображении также должен быть , и, возможно, аналогичным образом в конечном регулярном выражении. εab
Блуждающая логика
Вы можете заставить алгоритм работать для любого числа начальных и конечных состояний, добавив новое начальное и новое конечное состояние и соединив их с исходными начальными и конечными состояниями с помощью -edges. Теперь удалите все исходные состояния. Затем выражение находится на единственном остающемся ребре от до . Конструкция не будет давать петли при или поскольку эти состояния не имеют входящего соотв. исходящие края. Или, если вы строгие, у них будут метки, представляющие пустой набор. q - ε q + q - q + q -q+qεq+qq+q
Хендрик Ян
1
Во втором примере все еще есть проблема: перед упрощением автомат принимает «ba», (1, 3, 1), но после упрощения - нет.
wvxvw
50

метод

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

Пусть NFA без -переходов. Для каждого состояния создайте уравнениеε q iA=(Q,Σ,δ,q0,F)εqi

Qi=qiaqjaQj{{ε}, qiF, else

где - множество конечных состояний, а означает, что существует переход от к помеченный . Если вы читаете как или (в зависимости от определения вашего регулярного выражения), вы видите, что это уравнение регулярных выражений.Fqiaqjqiqja+

Для решения системы вам нужны ассоциативность и дистрибутивность и (конкатенация строк), коммутативность и лемма Ардена ¹:

Пусть регулярные языки с . Затем, ε UL,U,VΣεU

L=ULVL=UV

Решением является набор регулярных выражений , по одному на каждое состояние . описывает именно те слова, которые могут быть приняты при запуске в ; поэтому (если - начальное состояние) является искомым выражением.QiqiQiAqiQ0q0


пример

Для ясности мы обозначим одноэлементные множества по их элементу, то есть . Пример из-за Георга Зецше.a={a}

Рассмотрим это NFA:

пример нфа
[ источник ]

Соответствующая система уравнений:

Q0=aQ1bQ2εQ1=bQ0aQ2Q2=aQ0bQ1

Теперь вставьте третье уравнение во второе:

Q1=bQ0a(aQ0bQ1)=abQ1(baa)Q0=(ab)(baa)Q0

Для последнего шага мы применим лемму Ардена с , и . Обратите внимание, что все три языка являются регулярными и , что позволяет нам применять лемму. Теперь мы включим этот результат в первое уравнение:L=Q1U=abV=(baa)Q0εU={ab}

Q0=a(ab)(baa)Q0baQ0bb(ab)(baa)Q0ε=((abb)(ab)(baa)ba)Q0ε=((abb)(ab)(baa)ba)(by Arden's Lemma)

Таким образом, мы нашли регулярное выражение для языка, принятого вышеупомянутым автоматом, а именно

((a+bb)(ab)(b+aa)+ba).

Обратите внимание, что он довольно лаконичен (сравните с результатами других методов), но не определяется однозначно; Решение системы уравнений с другой последовательностью манипуляций приводит к другому - эквивалентно! - выражения.


  1. Доказательство леммы Ардена см. Здесь .
Рафаэль
источник
1
Какова временная сложность этого алгоритма? Есть ли ограничение на размер производимого выражения?
Jmite
@jmite: понятия не имею. Я не думаю, что попытался бы реализовать это (другие методы кажутся более осуществимыми в этом отношении), но использую его как метод с ручкой и бумагой.
Рафаэль
1
Вот реализация этого алгоритма на Прологе: github.com/wvxvw/intro-to-automata-theory/blob/master/automata/… но его maybe_union/2предикат может использовать больше работы (особенно если исключить общий префикс) для создания более регулярных регулярных выражений. Другой способ увидеть этот метод состоит в том, чтобы понимать его как перевод с регулярного выражения в прямолинейную грамматику, где языки с Prolog-подобным объединением или ML-подобным сопоставлением с образцом создают очень хорошие преобразователи, так что это не только перо и бумага алгоритм :)
wvxvw
Всего один вопрос. Ε в первом уравнении, потому что Qo является начальным состоянием или потому что это конечное состояние? То же самое, если у меня есть два последних состояния?
Georgio3
@PAOK Проверьте определение выше (строка); это потому, что является конечным состоянием. Qiq0
Рафаэль
28

Алгебраический метод Бжозовского

Это тот же метод, который описан в ответе Рафаэля , но с точки зрения систематического алгоритма, а затем и самого алгоритма. Оказывается, это легко и естественно реализовать, если вы знаете, с чего начать. Также это может быть проще, если рисовать все автоматы по какой-то причине нецелесообразно.

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

Идея алгоритма

Я не буду описывать, как это работает, поскольку это хорошо сделано в ответе Рафаэля, который я предлагаю прочитать раньше. Вместо этого я сосредотачиваюсь на том, в каком порядке вы должны решать уравнения, не делая слишком много дополнительных вычислений или дополнительных случаев.

Исходя из оригинального решения правила Ардена к уравнению языка мы можем рассматривать автомат как систему уравнений вида:X=ABX=AXB

Xi=Bi+Ai,1X1++Ai,nXn

мы можем решить это путем наведения на , обновив массивы и соответственно. На шаге имеем:nAi,jBi,jn

Xn=Bn+An,1X1++An,nXn

и правило Ардена дает нам:

Xn=An,n(Bn+An,1X1++An,n1Xn1)

и установив и мы получим:Bn=An,nBnAn,i=An,nAn,i

Xn=Bn+An,1X1++An,n1Xn1

и тогда мы можем удалить все потребности в системе, установив для :Xni,j<n

Bi=Bi+Ai,nBn
Ai,j=Ai,j+Ai,nAn,j

Когда мы решили когда , мы получили уравнение, подобное этому:Xnn=1

X1=B1

без . Таким образом, мы получили наше регулярное выражение.A1,i

Алгоритм

Благодаря этому мы можем построить алгоритм. Чтобы иметь то же соглашение, что и в приведенной выше индукции, мы будем говорить, что начальное состояние - а число состояний - . Во-первых, инициализация для заполнения :q1mB

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, все для печати или для примера Рафаэля. Обратите внимание, что есть удивительно эффективная функция упрощения регулярных выражений simple_re.

jmad
источник
4
Ссылка мертва ...
Коломбо
Реализация в Javascript: github.com/devongovett/regexgen/blob/master/src/regex.js
cakraww
24

Метод транзитивного закрытия

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

Ключевая идея

Пусть представляет регулярное выражение для строк, идущих от к используя состояния . Пусть будет числом состояний автомата.Ri,jkqiqj{q1,,qk}n

Предположим, что вы уже знаете регулярное выражение от до без промежуточного состояния (кроме конечностей) для всех . Затем вы можете догадаться, как добавление другого состояния повлияет на новое регулярное выражение : оно изменяется только при наличии прямых переходов к и может быть выражено так:Ri,jqiqjqki,jRi,jqk

Ri,j=Ri,j+Ri,k.Rk,k.Rk,j

( является и является .)RRk1RRk

пример

Мы будем использовать тот же пример, что и в ответе Рафаэля . Во-первых, вы можете использовать только прямые переходы.

Вот первый шаг (обратите внимание, что сам цикл с меткой преобразовал бы первый в .aε(ε+a)

R0=[εabbεaabε]

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

От до : .q 2 R 1 2 , 2 = R 0 2 , 2 + R 0 2 , 1 R 0 1 , 1R 0 1 , 2 = ε + b ε a = ε + b aq2q2R2,21=R2,20+R2,10R1,10R1,20=ε+bεa=ε+ba

Почему это? Это происходит потому, что переход от к с использованием только в качестве промежуточного состояния можно выполнить, оставаясь здесь ( ) или переходя к ( ), повторяя там ( ) и возвращаясь ( ).q2q2q1εq1aεb

R1=[εabbε+baa+bbab+aaε+ab]

Вы можете также вычислить и , и даст вам окончательное выражение, потому что является как начальным, так и конечным. Обратите внимание, что здесь было сделано много упрощений выражений. В противном случае первый из был бы а первый из был бы .R 3 R 3 1 , 1 1 a R 0 ( + a ) a R 1 ( ( + a ) + ε ( ε ) a )R2R3R1,131aR0(+a)aR1((+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)

Тогда конечное выражение (предположим, что является начальным состоянием):qs

e := ∅
for i = 1 to n:
  if final(i):
    e := e + R[s,i,n]

Но вы можете себе представить, что он генерирует некрасивые регулярные выражения. Вы действительно можете ожидать такие вещи, как который представляет тот же язык, что и . Обратите внимание, что упрощение регулярного выражения полезно на практике.a a()+(a+())(ε)(a+)aa

jmad
источник