3.2. Від регулярного виразу до нка
Є чимало стратегій побудови розпізнавача на основі регулярних виразів; кожна з них має свої переваги й недоліки. Одна зі стратегій, використовувана в багатьох текстових редакторах, полягає в побудові НКА на основі регулярного виразу й наступному моделюванні його поводження із застосуванням алгоритмів 3.3 й 3.4. Є й альтернативні реалізації ДКА по регулярних виразах, у яких НКА явно не будується. Цей розділ завершується обговоренням питань швидкості роботи й розміру пам'яті при реалізації розпізнавачів на основі НКА й ДКА.
3.2.1 Побудова нка по регулярному виразу
Зараз ми розглянемо алгоритм побудови НКА на основі регулярного виразу. Є багато варіацій цього алгоритму, і тут буде представлений найпростіший варіант, легкий у реалізації. Алгоритм синтаксично керований, оскільки використає в процесі побудови синтаксичну структуру регулярного виразу. Розгалуження алгоритму відповідають розгалуженням визначення регулярного виразу. Спочатку покажемо, як будується автомат для розпізнавання ε й будь-якого символу з алфавіту; потім визначимо, як конструюється автомат для виразів, що містять оператори об'єднання, конкатенації або замикання Кліні. Наприклад, для виразу r і s індуктивно побудуємо НКА з автоматів для r і s.
У процесі створення НКА кожен крок вносить не більше двох нових станів. У результаті НКА, побудований по регулярному виразу, має кількість станів, що перевищує число символів й операторів у регулярному виразу не більш ніж у два рази.
Алгоритм 3.3. Побудова НКА на основі регулярного виразу (побудова Томпсона)
Вхід. Регулярний вираз r над алфавітом V.
Вихід. НКА N, що допускає L(r).
Метод. Спочатку ми розбираємо r на складові підвирази. Потім, використовуючи наведені нижче правила (1) і (2), будуємо НКА для кожного базового символу в r (які являють собою символи алфавіту або ε). Базові символи відповідають частинам (1) і (2) у визначенні регулярного виразу. Важливо розуміти, що якщо в регулярному виразі r деякий символ а зустрічається кілька разів, для кожного його входження створюється окремий НКА.
Потім, послідовно з синтаксичною структурою регулярного виразу r, індуктивно комбінуємо ці НКА з використанням правила (3), поки не буде отриманий НКА для всього виразу. Кожен проміжний НКА відповідає своєму підвиразу r і має кілька важливих властивостей: такий автомат має рівно один заключний стан, жодна дуга не входить у початковий стан і жодна не виходить із заключного.
1. Для ε будуємо НКА.
Тут i- новий початковий стан, а f – новий заключний. Очевидно, що цей НКА розпізнає {ε}.
2. Для а є V будуємо НКА
start
Тут також i - новий початковий стан, а f – новий заключний. Цей НКА розпізнає {а}.
3. Нехай N(s) і N(t) являють собою НКА для регулярних виразів s й t.
а) Для регулярного виразу s|t будуємо наступній складений НКА N(s|t):
Тут i— новий початковий стан, а f – новий заключний. Є переходи по ε від i до стартових станів N(s) і N(t) а також переходи по ε із заключних станів N(s) і N(t) у новий заключний стан f. Початкові й заключні стани N(s) і N(t) не збігаються з початковими й заключними станами N(s|t). Помітимо, що будь-який шлях від і до f повинен проходити або тільки через N(s), або тільки через N(t). У відповідності зі сказаним складений НКА розпізнає L(s)\ L(t).
b) Для регулярного виразу st будується складений НКА N(st):
start
Початковий стан N(s) стає початковим станом складеного НКА, а заключний стан N(t)— заключним станом складеного автомата. Заключний стан N(s) зливається з початковим N(t); таким чином, всі переходи з початкового стану N(t) перетворяться в переходи із заключного стану N(s). Новий об'єднаний стан втрачає свій статус початкового/заключного в комбінованому автоматі. Шлях від i до f повинен пройти спочатку через N(s), а потім — через N(t), так що мітки на цьому шляхи являють собою рядок з L(s)L(t). Оскільки немає дуг, які входять у початковий стан N(t) або залишають заключний N(s), не існує шляху від i до f, який би повертався від N(t) до N(s). Отже, складений НКА розпізнає L(s)L(t).
с) Для регулярного виразу s* побудуємо складений НКА N(s*)
Тут i— новий початковий стан, а f – новий заключний. У складеному НКА можемо пройти від i до f або прямо, уздовж дуги ε, що представляє той факт, що ε € (L(s))*, або через N(s) один або більше раз. Очевидно, що складений НКА розпізнає (L(s))* .
d) Для регулярного виразу в дужках (s) у якості НКА використаємо N(s).
Кожен новий стан одержує індивідуальне ім'я. Таким чином, ніякі зі станів кожного з компонентів НКА не можуть мати те саме ім'я. Навіть якщо в r кільки разів зустрічається той самий символ, для кожного екземпляра цього символу створюється окремий НКА із власними станами. D
Ми можемо переконатися, що кожен крок побудови в алгоритмі 3.3 дає НКА, що розпізнає коректну мову. Крім того, побудова приводить до НКА N(r) з наступними властивостями.
N(r) має кількість станів, що перевищує кількість символів й операторів у r не більш ніж у два рази. Це випливає з того, що на кожному етапі побудови створюється не більше двох нових станів.
N(r) має рівно один початковий й один заключний стан. Заключний стан не має вихідних переходів. Це властивість вірна й для всіх складових автоматів.
Кожен стан N(r) має або один вихідний перехід для символу з V, або не більше двох вихідних ε-переходів.
Приклад 3.4
Застосуємо алгоритм 3.3 до побудови N(r) для регулярного виразу r = (a|b)*abb. На рис. 3.4 показане дерево розбору для r, аналогічне деревам, створеним для арифметичних виразів у розділі 2.2. Для складової r1, першого а, будуємо НКА
start
Для r2 НКА виглядає як
НКА для (r3) той же, що й для r3, а для (r3)* він виглядає в такий спосіб:
start
Щоб одержати автомат для r5 r6, потрібно об'єднати стани 7 й 7' у єдиний стан 7. У результаті одержуємо НКА
Продовжуючи роботу, одержимо НКА для r11 = (a|b)*abb, що показаний на рис. 3.5.
Рис. 3.5. Граф переходів НКА для регулярного виразу (a|b)*abb
Як видно, цей граф відрізняється від графа переходів, що був наведений на рис. 3.1, для того ж самого регулярного виразу.
