Приведем теперь алгоритм построения детерминированного конечного автомата по регулярному выражению. К регулярному выражению (сокращенно PB) r добавим маркер конца: (r)#. После построения ДКА для расширенного РВ легко построить ДКА для исходного РВ: все состояния ДКА, из которых есть переход в конечное с чтением символа "#", можно считать конечными, а символ "#" и соответствующие переходы удалить.

Представим РВ в виде дерева, листья которого — терминальные символы, а внутренние вершины- операции "." (конкатенации), "U" (объединение), "*" (итерация).

Каждому листу дерева (кроме е-листьев) припишем уникальный номер и ссылаться на него будем, с одной стороны, как на позицию в дереве и, с другой стороны, как на позицию символа,. соответствующего листу.

Теперь, обходя дерево Т сверху-вниз слева-направо, вычислим четыре функции: nullable, firstpos, lastpos, followpos. Функции nullable, firstpos и lastpos определены на узлах дерева, а followpos - на множестве позиций. Значением всех функций, кроме nullable, является множество позиций. Функция followpos вычисляется через три остальные функции.

Функция firstpos(n) для каждого узла n синтаксического дерева регулярного выражения дает множество позиций, которые соответствуют первым символам в подцепочках, генерируемых подвыражением с вершиной в n. Аналогично, lastlog(n) дает множество позиций, которым соответствуют последние символы в  подцепочках, генерируемых подвыражениями с вершиной п. Для узлов n, поддеревья которых ( т.е. дерево, у которого узел n является корнем) могут породить пустое слово, определим nullable(n)=true, а для остальных узлов false.

Таблица для вычисления функций nullable и firstpos приведена на рисунке 54. Вычисление функции lastpos строится аналогично.

 

z54

Рисунок 54

Пример. Функции firstpos  и lastpos для выражения (a+b)abb# приведены на рисунке 55. Слева от каждой вершины значение firstpos, справа- lastpos. Заметим, что эти функции могут быть вычислены за один проход дерева.

Если i- позиция, то followpos(i) есть множество позиций j таких, что существует некоторая строка ...cd..., входящих в язык, описываемый PB, такая, что i соответствует этому вхождению c, а j- вхождению d.

Функция followpos может быть вычислена также за один обход дерева по следующим двум правилам.

1. Пусть n - внутренний узел с операцией "." (конкатенация), a, b - его потомки. Тогда для каждой позиции i, входящей в lastpos(a), добавляем к множеству значений  followpos(i) множество  firstpos (b).

2. Пусть n — внутренний узел с операцией "*" (итерация), а – его потомок. Тогда для каждой позиции i, входящей в lastpos (а),  добавляем к множеству значений followpos(i) множество  firstpos(a).

Для примера  значения функции followpos приведены на рисунке 55.

 

z55

Рисунок 55

Функция followpos позволит теперь сразу построить детерминированный конечный автомат с помощью следующего алгоритма.

Алгоритм. Прямое построение  ДКА по регулярному выражению.
Будем строить множество состояний автомата  Dstates и помечать их. Состояния ДКА соответствуют множествам позиций. Начальным состоянием будет состояние  firstpos(root), где root - вершина синтаксического дерева регулярного выражения, конечными — все  состояния, содержащие позиции, связанные с символом "#".

Сначала в Dstates имеется только одно непомеченное состояние firstpos(root).

while есть непомеченное состояние Т в  Dstates do

пометить Т;

for каждого входного символа а<-Т do

пусть символу а в Т соответствуют позиции

р1 ,...,рi, и пусть  S=Ui followpos(pi)

Если S не пусто и S не принадлежит  Dstates, то

добавить непомеченное состояние S в  Dstates (рисунок 56)

Функцию перехода  Dtran для Т и а определить как Dtran(T,a)=S.

end;

end;

 

Для нашего примера вначале Т={1(f),2(b),3(a)}. Последовательность шагов алгоритма приведена на рисунке 56. В результате будет построен детерминированный конечный автомат, изображенный на рисунке 57. Состояния автомата обозначаются как множества позиций, например {1,2,3}, конечное состояние заключено в квадратные скобки [1,2,3,6].

 

z56

Рисунок 56

 

z57

Рисунок 57