3.3 Детерміновані кінцеві автомати
Детермінований кінцевий автомат (deterministic finite automaton, DFA) , для якого далі в тексті використається скорочення ДКА, є спеціальним випадком недетермінованого кінцевого автомата, у якого:
відсутні стани, що мають ε-переходи;
для кожного стану s і вхідного символу а існує не більше однієї дуги, що виходить з s і позначеної як а.
Детермінований кінцевий автомат має для будь-якого вхідного символу не більше одного переходу з кожного стану. Якщо для подання функції переходів ДКА використається таблиця, то кожен запис у ній являє собою єдиний стан. Отже, дуже просто перевірити, чи допускає даний ДКА деякий рядок, оскільки є не більше одного шляху від стартового стану, позначеного цим рядком. Наступний алгоритм імітує поводження ДКА при обробці вхідного рядка.
Алгоритм 3.1. Моделювання дка
Вхід. Вхідний рядок х, що завершується символом кінця файлу eof, і ДКА D зі стартовим станом s0 і множиною заключних станів F.
Вихід. "Так", якщо D допускає х, і "ні" у противному випадку.
Метод. До вхідного рядка х застосовується алгоритм, наведений на рис. 3.6.
Функція move(s,с) дає стан, у який відбувається перехід зі стану s при вхідному символі с. Функція nextchar повертає черговий символ рядка х.
s := s0 ;
с := nextchar;
while c ≠ eof do begin
s := move(s,c) ;
с := nextchar;
end;
if sєF then
return "так"
else return "ні"
Рис. 3.6. Імітаційне моделювання ДКА
Приклад 3.2
На рис. 3.7 показаний граф переходів детермінованого кінцевого автомата, що допускає ту ж мову (a|b)*abb, що й НКА на рис. 3.1. При роботі із цим ДКА й вхідним рядком аbаbb алгоритм 3.4 пройде послідовність станів 0, 1, 2, 1, 2, 3 і відповість "так".
a a
Рис. 3.7. ДКА, що допускає рядок (a|b)*abb
Таблиця переходів для цього ДКА наведено нижче.
-
Стан
Вхідний символ
a
b
0
1
0
1
1
2
2
1
3
3
1
0
3.3 Перетворення нка в дка
Звернемо увагу на те, що НКА на рис 3.1 має два переходи зі стану 0 для вхідного символу а — у стани 0 або 1. Точно так само НКА на рис. 3.3 має два переходи для ε зі стану 0. Хоча це й не показано на прикладі, ситуація, коли можна вибрати перехід для ε або реально уведеного символу, також неоднозначна. Ситуації, у яких функція переходів багатозначна, роблять моделювання НКА за допомогою комп'ютерної програми досить складним завданням. Визначення припустимості затверджує тільки, що повинен існувати деякий шлях, позначений вхідним рядком і ведучий від початкового до заключного стану. Однак коли є багато шляхів для одного й того ж вхідного рядка, можливо, прийдеться розглядати їх всі, щоб знайти шлях до заключного стану або з'ясувати, що такого шляху не існує.
Зараз розглянемо алгоритм для перетворення НКА в ДКА, що розпізнає ту ж мову, що й НКА. Цей алгоритм, часто називаний побудовою підмножини (subset construction), корисний при моделюванні НКА комп'ютерною програмою. Тісно пов'язаний з ним алгоритм відіграє важливу роль у побудові LR-аналізаторів, розглянутих у наступних главах.
У таблиці переходів НКА кожен запис являє собою множину станів; у таблиці переходів ДКА — єдиний стан. Загальна ідея перетворення НКА в ДКА полягає в тому, що кожен стан ДКА відповідає множині станів НКА. ДКА використає свої стани для відстеження всіх можливих станів, у яких НКА може перебувати після читання чергового вхідного символу. Таким чином, після читання вхідного потоку а1,а2...аn ДКА перебуває в стані, що представляє підмножину Т станів НКА, досяжних зі стартового стану НКА по шляху, позначеному як а1,а2...аn. Кількість станів ДКА може виявитися експоненційно залежної від кількості станів НКА, але на практиці цей найгірший випадок зустрічається вкрай рідко.
Алгоритм 3.2. Побудова ДКА із НКА (побудова підмножини)
Вхід. НКА N.
Вихід. ДКА D, який допускає ту ж мову, що і N.
Метод. Даний алгоритм будує таблицю переходів Dtran для D. Кожен стан ДКА є елементом множини станів НКА, і ми будуємо Dtran так, щоб D "паралельно" моделював всі можливі переміщення N по даному вхідному рядку.
Операція |
Опис |
ε-closure(s) (ε-замикання(s)) |
Множина станів НКА, досяжних зі стану s тільки по ε-переходам |
ε-closure(Т) (ε-замикання(Т)) |
Множина станів НКА, досяжних з усіх станів s із Т тільки по ε-переходам |
move(T, a) |
Множина станів НКА, у які є перехід з усіх станів s із Т по вхідному символі а |
Рис. 3.8 Операції над станами НКА
Для відстеження множин станів НКА використаємо операції, що наведені на рис. 3.8 (s представляє стан НКА, а Т — множину станів НКА).
Перед тим, як розглядати перший вхідний символ, N може бути в кожному зі станів множини ε-closure(s0), де s0 — стартовий стан N. Припустимо, що стани множини Т, і тільки вони, досяжні з s0 після прочитання даної послідовності вхідних символів, і нехай а — наступний вхідний символ. Одержавши a, N може переміститися в будь-який стан з множини move(T,а). Зробивши із цих станів всі можливі ε -переходи, N після обробки а може бути в будь-якому стані з ε-closure(move(T, a)).
// Спочатку ε-closure(s0) є єдиним станом в Dstates і непомічений;
while в Dstates є непомічений стан T do
begin
позначити Т;
for кожен вхідний символ a do
begin
U:= ε-closure(move(T, a));
if U NOT є Dstates then
Додати U як непомічений стан в Dstates; Dtran[T,a]:= U
end
end
Рис. 3.9. Побудова підмножини
Множина станів Dstates автомата D і таблицю його переходів Dtran можна створити в такий спосіб. Кожен стан D відповідає множині станів НКА, у яких може перебувати N після читання деякої послідовності вхідних символів, включаючи всі можливі ε-переходи до й після прочитаних символів. Стартовий стан D — ε-closure(s0). Стани й переходи додаються в D відповідно до алгоритму, наведеному на рис. 3.9. Стан D є таким, що допускає, якщо він являє собою множину станів НКА, що містять як мінімум один стан N, що допускає.
Обчислення ε-closure(T) є типовим процесом пошуку графа для вузлів, що можуть досягатися з даної множини вузлів. У цьому випадку стану Т представляють дану множину вузлів, а граф складається тільки з дуг НКА, позначених ε. Простий алгоритм обчислення ε-closure(T) використовує стек для зберігання станів, дуги яких не були перевірені на наявність ε-переходів. Така процедура показана на рис. 3.10.
Внести всі стани множини Т у стек stack;
ініціалюзовати ε-closure(Т) множиною Т; while stack не порожній do
begin
зняти зі стека верхній елемент t for кожен стан u з дугою
від t до ε, позначеною ε do
if u not€ ε-closure(T) then
begin додати u до ε-closure(T); помістити U в stack
end
end
Рис. 3.10. Обчислення ε-замикання
Приклад 3.3
На рис. 3.11 показаний НКА N (рис. 3.5), що був отриманий шляхом перетворення регулярного виразу (a|b)*abb. Застосуємо до N алгоритм 3.2.
Рис. 3.11 HKA N для (а|b)*abb
Стартовий стан еквівалентного ДКА являє собою ε-closure(0), тобто А= {0, 1, 2, 4, 7}, оскільки саме ці стани досяжні зі стану 0 шляхами, у яких кожна дуга позначена ε. Помітимо, що шлях може й не мати дуги, так що стан 0 також досягається зі стану 0 і, тому, входить у шукану множину.
Алфавіт вхідних символів являє собою {а,b}, За допомогою алгоритму на рис. 3.7 можна позначити А и обчислити ε-closure(move(A, а)). Обчислимо move(A,а). За визначенням (див. Таблицю на рис. 3.6) – це множина станів N, що мають переходи по символу а для елементів множини А. Зі станів 0, 1, 2, 4 й 7 тільки 2 й 7 мають такі переходи до станів 3 й 8, тому
move(A,а) = move({0,l,2,4,7},а) = {3,8}, а ε-closure({3,8}) = {1,2,3,4,6,7,8}, тобто ε-closure(move(A,a)) = ε-closure(move({0,l,2,4,7},a)) = {1,2,3,4,6,7,8} .
Оскільки ця множина не співпадає з множиною A, то це новий непомічений стан ДКА. Назвемо цю множину В. Таким чином, Dtran[A,a] = В.
Серед станів в А тільки стан 4 має перехід по b у стан 5, тобто
move(A,b) = move({0,l,2,4,7},b) = {5}, а ε-closure({5}) = {1,2,4,5,6,7}. Це нова множина, назвемо її С. Таким чином, Dtran{A, b} = С.
Продовжимо цей процес із непоміченими в даний момент множинами (станами ДКА) В й С.
4) ε-closure(move(В,a)) = ε-closure(move({1,2,3,4,6,7,8},a)) = ε-closure({3,8}) = {1,2,3,4,6,7,8}= В. Таким чином, Dtran[В,a] = В.
5) ε-closure(move(В,b)) = ε-closure(move({1,2,3,4,6,7,8},b)) = ε-closure({5,9}) = {1,2,,4,5,6,7,9}. Це нова множина, назвемо її D. Таким чином, Dtran{B, b} = D.
Далі наведемо операції алгоритма без коментарів.
6) ε-closure(move(С,a)) = ε-closure(move({1,2,4,6,7},a)) = ε-closure({3,8}) = {1,2,3,4,6,7,8}= В. Dtran[С,a] = В.
7) ε-closure(move(С,b)) = ε-closure(move({1,2,4,6,7},b)) = ε-closure({5}) = {1,2,4,5,6,7} = С. Dtran[C, b] = C.
8) ε-closure(move(D,a)) = ε-closure(move({1,2,,4,5,6,7,9},a)) = ε-closure({3,8}) = {1,2,3,4,6,7,8}= В. Dtran[D,a] = В.
9) ε-closure(move(D,b)) = ε-closure(move({1,2,,4,5,6,7,9},b)) = ε-closure({5,10}) = {1,2,4,5,6,7,10}= E. Dtran[D,b] = E.
10) ε-closure(move(E,a)) = ε-closure(move({1,2,,4,5,6,7,10},a)) = ε-closure({3,8}) = {1,2,3,4,6,7,8}= В. Dtran[E,a] = В.
11) ε-closure(move(E,b)) = ε-closure(move({1,2,
4,5,6,7,10},a)) = ε-closure({5}) = {1,2,4,5,6,7} = С. Dtran[E, b] = C.
В остаточному підсумку всі множини-стани ДКА виявилися позначеними. Це безперечно, оскільки всього є 211 різних підмножин множини з одинадцяти станів, а один раз позначена множина залишається такою назавжди. У дійсності для даного прикладу створилося п'ять різних множин станів:
А= {0,1,2,4,7} В={1, 2, 3, 4, 6, 7, 8} С={1,2,4,5,6,7}
D={1, 2, 4,5,6,7,9} Е= {1,2, 4, 5, 6, 7, 10}
Стан А є початковим, а Е— єдиним заключним станом.
Повністю таблиця переходів Dtran показана на рис. 3.12.
Стан |
|
Вхідний символ |
|
а |
|
b |
|
А |
В |
|
С |
В |
В |
|
D |
С |
В |
|
С |
D |
В |
|
Е |
Е |
В |
|
С |
Рис. 3.12. Таблиця переходів Dtran для ДКА
Граф переходів отриманого в результаті перетворення ДКА показаний на рис. 3.13. Треба замітити, що ДКА на рис. 3.5 також допускає мову (a|b)*abb і має на один стан менше. Питання мінімізації кількості станів ДКА буде розглянуте пізніше.
a a
Рис.3.13. Результат застосування побудови підмножини до рис. 3.11
