Як перетворити кінцеві автомати в регулярні вирази?


115

Перетворення регулярних виразів у (мінімальний) NFA, які приймають ту саму мову, легко за допомогою стандартних алгоритмів, наприклад алгоритму Томпсона . Інший напрямок, здається, є більш втомливим, але іноді отримані вирази є безладними.

Які алгоритми існують для перетворення NFA в еквівалентні регулярні вирази? Чи є переваги щодо часової складності чи розміру результату?

Це має бути еталонним питанням. Будь ласка, включіть загальне роз'яснення вашого методу, а також нетривіальний приклад.


2
Зверніть увагу на подібне запитання на сайті cstheory.SE, яке, мабуть, не підходить нашій аудиторії.
Рафаель

всі відповіді використовують формальну техніку для написання RE від DFA. Я вважаю, що моя техніка за допомогою аналізу є порівняно простою та об'єктивною, яку я демонструю у своїй відповіді: Яка мова цих детермінованих кінцевих автоматів? Я думаю, що це буде корисно колись. Так, звичайно, колись я використовую формальний метод (теорема Ардена), щоб написати RE - це питання складне, як наведено в цьому прикладі: Як написати регулярний вираз для DFA
Grijesh Chauhan

Відповіді:


94

Існує кілька методів перетворення з кінцевих автоматів у регулярні вирази. Тут я опишу те, що зазвичай викладають у школі, і це дуже наочно. Я вважаю, що це найбільше використовується на практиці. Однак написання алгоритму - не така гарна ідея.

Державний метод видалення

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

Ключова ідея

Ідея полягає в тому, щоб розглянути регулярні вирази на ребрах, а потім видалити проміжні стани, дотримуючись послідовності міток ребер.

Основний візерунок можна побачити на малюнках нижче. Перший має мітки між які є регулярними виразами e , f , g , h , i, і ми хочемо видалити q .p,q,re,f,g,h,iq

pqr автомат

Після вилучення ми складаємо разом (зберігаючи інші ребра між p і r, але це не відображається на цьому):e,f,g,h,ipr

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

Приклад

Використовуючи той же приклад, що і у відповіді Рафаеля :

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

ми послідовно видаляємо :q2

1-3 автомат

а потім :q3

1 автомат

тоді нам ще належить застосувати зірку на вираз від до q 1 . У цьому випадку кінцевий стан також є початковим, тому нам дійсно просто потрібно додати зірку:q1q1

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

Алгоритм

L[i,j]- це зворотне вираження мови від до q j . Спочатку видаляємо всі багатокрайники:qiqj

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=∅εqiqkqjqk

Тепер, як користуватися remove(k)? Не слід видаляти остаточні або початкові стани злегка, інакше ви пропустите частини мови.

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

Якщо у вас є лише один кінцевий стан та один початковий стан то остаточним виразом є:qfqs

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

Це та факт, що це зміна мов більш динамічно, ніж перший метод, роблять її більш схильною до помилок при програмуванні. Я пропоную використовувати будь-який інший метод.

Мінуси

У цьому алгоритмі дуже багато випадків, наприклад, для вибору, який вузол нам слід видалити, кількість кінцевих станів наприкінці, те, що кінцевий стан може бути початковим, і т.д.

Зауважимо, що тепер, коли алгоритм написаний, це дуже схоже на метод перехідного закриття. Тільки контекст використання інший. Я не рекомендую реалізувати алгоритм, але використовувати метод, щоб зробити це вручну, - це гарна ідея.


1
У прикладі, 2-е зображення, після видалення вузла "2", у вузлі А. відсутній край - край петлі (ab)
Панос Кал.

@Kabamaru: виправлено. Але зараз я думаю, що на 3-му зображенні також повинен бути , і, можливо, в остаточному регулярному виразі. εab
Мандрівна логіка

Ви можете змусити алгоритм працювати для будь-якої кількості початкових і кінцевих станів, додавши новий початковий і новий кінцевий стан і з'єднавши їх з початковим початковим і кінцевим станами за допомогою -edge. Тепер видаліть усі початкові стани. Потім вираз знаходимо на одному решті, що залишився від до . Конструкція не дасть циклів при або оскільки ці стани не мають вхідного відтворення. вихідні краї. Або якщо ви суворі, вони матимуть мітки, що представляють порожній набір. q+qεq+qq+q
Гендрик Ян

1
Існує проблема з другим прикладом: перед спрощенням автомати приймають "ba", (1, 3, 1), але після спрощення цього не відбувається.
wvxvw

50

Метод

Найприємніший метод, який я бачив, - це той, що виражає автомат як систему рівнянь (регулярних) мов, яку можна вирішити. Це особливо приємно, оскільки, здається, дає більш стислі вирази, ніж інші методи.

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

Qi=qiaqjaQj{{ε}, qiF, else

де - набір кінцевих станів, а означає, що існує перехід від до позначений . Якщо ви читаєте як або (залежно від вашого регулярного визначення виразу), ви бачите, що це рівняння регулярних виразів.Fqiaqjqiqja+

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

Нехай регулярні мови з . Тоді,L,U,VΣεU

L=ULVL=UV

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


Приклад

Для наочності позначаємо одиничні множини їх елементом, тобто . Приклад - Георг Зецше.a={a}

Розглянемо це NFA:

приклад 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
Ось реалізація цього алгоритму Prolog: github.com/wvxvw/intro-to-automata-theory/blob/master/automata/…, але його maybe_union/2предикат може використовувати більше роботи (наприклад, wrt усунення загального префікса) для створення більш чітких регулярних виразів. Інший спосіб бачити цей метод - це зрозуміти його як переклад з регулярного вираження в правильну лінійну граматику, де мови з прологічною уніфікацією або схожою на 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.


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

Від до : .q2q2R2,21=R2,20+R2,10R1,10R1,20=ε+bεa=ε+ba

Чому так? Це тому, що перехід від до використовуючи лише як проміжний стан, можна зробити, залишившись тут ( ) або перейшовши до ( ), там ( ) і повернувшись назад ( ).q2q2q1εq1aεb

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

Ви можете обчислити так, що і теж вийде , і дасть вам остаточний вираз, оскільки є початковим і кінцевим. Зауважте, що тут було зроблено багато спрощень виразів. В іншому випадку перший з буде і першим з буде .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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.