Алгоритм
Для начала модифицируем алгоритм Эрли. Главным отличием от базовой версии алгоритма является функция [math]\mathtt{rulesLoop}[/math], внутри которой мы, как и в базовой версии, просматриваем второе и третье правило, однако, в отличие от базовой версии, где при каждом изменении [math]D_j[/math] мы просматривали весь список [math]D_j[/math] и применяли к нему второе и третье правило, в модифицированной версии мы применяем правила внутри [math]\mathtt{rulesLoop}[/math], просматривая только те ситуации, которые были добавлены на предыдущей итерации цикла [math]\mathtt{while}[/math].
Будем рассматривать грамматику без ε-правил и бесполезных символов.
function [math]\mathtt{earleyMod}(G, w)[/math]:
// Инициализация
[math] D_{0} = \lbrace [S' \rightarrow \cdot S, 0] \rbrace [/math]
rulesLoop(0)
for j = 1 .. n
for [math][A \rightarrow \alpha \cdot a_{j} \beta, i] \in D_{j-1}[/math]
[math]D_j[/math] [math] \cup[/math] = [math][A \rightarrow \alpha a_{j} \cdot \beta, i][/math] // Первое правило
rulesLoop(j)
function [math]\mathtt{rulesLoop(j)}[/math]:
[math]D_j'' = D_j[/math]
while [math]D_j'' \ne \varnothing[/math]
[math]D_j' = D_j''[/math]
[math]D_j'' = \varnothing[/math]
for [math][B \rightarrow \eta \cdot , i] \in D_j'[/math] // Цикл (*)
for [math][A \rightarrow \alpha \cdot B \beta, k] \in D_{i}[/math]
[math]D_j''[/math] [math] \cup[/math] = [math][A \rightarrow \alpha B \cdot \beta, k] [/math] // Второе правило
for [math][B \rightarrow \alpha \cdot A \eta, k] \in D_j'[/math] // Цикл (**)
for [math]\beta : (A \rightarrow \beta) \in P[/math]
[math]D_j''[/math] [math] \cup[/math] = [math][A \rightarrow \cdot \beta, j][/math] // Третье правило
[math]D_j[/math] [math] \cup[/math] = [math]D_j''[/math]
Доказательство эквивалентности
В циклах, помеченных [math](*)[/math] и [math](**)[/math], просматривается не весь список [math]D_j[/math], а только те ситуации, которые были добавлены на предыдущей итерации цикла [math]\mathrm{while}[/math]. Данная модификация является корректной.
- Рассмотрим цикл [math](*)[/math]. Если в текущей ситуации [math][B \rightarrow \eta \cdot, i][/math] этого цикла [math]i \ne j[/math], то во внутреннем цикле просматривается список с меньшим индексом, в который новые ситуации больше не добавляются. Поэтому после первого просмотра этого списка будут добавлены все ситуации, удовлетворяющие условию, и больше ситуацию [math][B \rightarrow \eta \cdot, i][/math] в цикле [math](*)[/math] рассматривать не нужно. Если же [math]i = j[/math], то [math]\eta \Rightarrow^* \varepsilon[/math], что возможно, только если [math]B = S', \eta = \varepsilon[/math]. Тогда во внутреннем цикле не будет добавлено ни одной ситуации, так как [math]S'[/math] не встречается в правых частях правил.
- Теперь рассмотрим цикл [math](**)[/math]. Так как для каждой ситуации [math][B \rightarrow \alpha \cdot A \eta, k][/math] в список добавляется новая ситуация, соответствующая правилу из грамматики, а грамматика фиксирована, то после первого просмотра будут добавлены все возможные ситуации для [math][B \rightarrow \alpha \cdot A \eta, k][/math].
Таким образом, во все списки будут добавлены ситуации, которые были бы добавлены в ходе обычного алгоритма. Очевидно, что лишних ситуаций добавлено не будет, так как в циклах [math](*)[/math] и [math](**)[/math] просматривается подмножество полного списка. Значит этот алгоритм эквивалентен оригинальному.
Время работы для однозначной грамматики
Лемма (1): |
[math]\forall\,j: 1 \leqslant j \leqslant n[/math] в списке [math]D_j[/math] находится [math]O(j)[/math] ситуаций. |
Доказательство: |
[math]\triangleright[/math] |
Так как грамматика фиксирована, то [math]\forall i[/math] количество ситуаций вида [math][A \rightarrow \alpha \cdot \beta, i][/math] не больше некоторой константы. Таким образом, поскольку в [math]D_j[/math] находятся ситуации, у которых [math]0 \leqslant i \leqslant j[/math], всего в [math]D_j[/math] будет [math]O(j)[/math] ситуаций. |
[math]\triangleleft[/math] |
Лемма (2): |
Пусть [math]\Gamma = (N, \Sigma, P, S)[/math] — однозначная КС-грамматика без непорождающих нетерминалов и [math]a_1 \dots a_n[/math] — цепочка из [math]\Sigma^*[/math]. Тогда алгоритм Эрли пытается включить [math][A \rightarrow \alpha \cdot \beta, i][/math] в [math]D_j[/math] не более одного раза, если [math]\alpha \ne \varepsilon[/math]. |
Доказательство: |
[math]\triangleright[/math] |
Ситуацию [math][A \rightarrow \alpha \cdot \beta, i][/math] можно включить в [math]D_j[/math] только по правилам [math](1)[/math] (если последний символ [math]\alpha[/math] — терминал) и [math](2)[/math] (если нетерминал). В первом случае результат очевиден. Во втором случае допустим, что [math][A \rightarrow \alpha'B \cdot \beta, i][/math] включается в [math]D_j[/math], когда рассматриваются две ситуации [math][B \rightarrow \eta_1 \cdot, k_1][/math] и [math][B \rightarrow \eta_2 \cdot, k_2][/math] (они различны, так как в цикле [math](*)[/math] каждая ситуация из каждого списка рассматривается по одному разу). Тогда ситуация [math][A \rightarrow \alpha' \cdot B\beta, i][/math] должна оказаться одновременно в [math]D_{k_1}[/math] и в [math]D_{k_2}[/math]. Таким образом, получаем:
- [math]\alpha' \Rightarrow^* a_{i+1} \ldots a_{k_1}[/math] и [math]\alpha' \Rightarrow^* a_{i+1} \ldots a_{k_2}[/math];
- [math]\eta_1 \Rightarrow^* a_{k_1+1} \ldots a_j[/math] и [math]\eta_2 \Rightarrow^* a_{k_2+1} \ldots a_j[/math].
Следовательно, [math]\alpha' \eta_1 \Rightarrow^* a_{i+1} \ldots a_j[/math] и [math]\alpha' \eta_2 \Rightarrow^* a_{i+1} \ldots a_j[/math].
Заметим, что [math]S \Rightarrow^* \gamma A \delta \Rightarrow^* a_1 \ldots a_i A \delta \Rightarrow a_1 \ldots a_i \alpha' B \beta \delta[/math]. Предположим, что [math]\beta \delta \Rightarrow^* w'[/math] (ведь в грамматике нет непорождающих нетерминалов). Тогда [math]S \Rightarrow^* a_1 \ldots a_i \alpha' \eta_1 w'[/math] и аналогично [math]S \Rightarrow^* a_1 \ldots a_i \alpha' \eta_2 w'[/math].
Таким образом, если [math]k_1 \ne k_2[/math], то подстрока [math]a_{i+1} \ldots a_j[/math] выводится двумя различными способами из [math]\alpha' \eta_1[/math] и [math]\alpha' \eta_2[/math] (поскольку в первом случае [math]\alpha' \Rightarrow^* a_{i+1} \ldots a_{k_1}[/math], а во втором [math]\alpha' \Rightarrow^* a_{i+1} \ldots a_{k_2}[/math]), то есть у строки [math]a_1 \ldots a_jw'[/math] есть два различных вывода, что противоречит однозначности грамматики. Если же [math]k_1 = k_2[/math], то [math]\eta_1 \ne \eta_2[/math], что приводит к аналогичному противоречию.
Суммируя выше сказанное, отметим, что противоречие получается из того факта, что в некоторый момент времени (то есть для подстроки [math]a_1 \dots a_i[/math]) мы получаем два различных дерева вывода. Поэтому, в дальнейшем, при выводе суффикса [math]a_{i+1} \dots a_n[/math], каким образом мы его не получим, деревьев вывода будет как минимум два, поскольку они будут получаться заменой какого-то листа (терминального символа) на какое-то правило (поддерево из нетерминалов и терминалов),таким образом, получаем противоречие с однозначностью (по определению неоднозачной грамматики) |
[math]\triangleleft[/math] |
Теорема: |
Если входная грамматика однозначна, то время выполнения алгоритма Эрли для слова длины [math]n[/math] составляет [math]O(n^2)[/math]. |
Доказательство: |
[math]\triangleright[/math] |
Орагнизуем каждый список разбора [math]D_j[/math] таким образом, чтобы по любому символу [math]x \in \Sigma \cup N[/math], можно было за [math]O(1)[/math] получить список тех и только тех ситуаций, содержащихся в [math]D_j[/math], которые имеют вид [math][A \rightarrow \alpha \cdot x \beta, j][/math].
Время построения [math]D_0[/math] не зависит от входной строки.
Рассмотрим [math]D_j, \, j \gt 0[/math].
- При включении ситуаций по правилу [math](1)[/math] необходимо лишь просмотреть предыдущий список и для каждого его элемента выполнить константное число операций.
- Рассмотрим правило [math](2)[/math]. Можно считать, что внутри цикла [math](*)[/math] рассматриваются те и только те ситуации, которые удовлетворяют условию (так как список таких ситуаций можно по нетерминалу получить за [math]O(1)[/math] следующим образом: каждый раз, когда мы добавляем ситацаию вида [math][A \rightarrow \alpha \cdot B \beta, i][/math] в [math]D_j[/math], мы просмотрим в заранее заготовленном массиве для [math]D_j[/math], есть ли в [math]D_j[/math] ситуации вида [math][B \rightarrow \eta \cdot, j][/math]. Если да, то добавим [math][A \rightarrow \alpha B \cdot \beta, i][/math] в [math]D_j[/math].). Тогда каждая такая ситуация будет добавлена в список и, исходя из леммы 2, попытка добавления будет единственной. А так как по лемме 1 всего в списке [math]D_j[/math] находится [math]O(j)[/math] ситуаций, то суммарно за все итерации внешнего цикла while внутри цикла [math](*)[/math] будет рассмотрено [math]O(j)[/math] ситуаций.
- Так как грамматика фиксирована, то при применении правила [math](3)[/math] при рассмотрении любой ситуации количество включаемых ситуаций не превосходит некоторой константы, поэтому для каждой рассмотренной ситуации будет выполнено [math]O(1)[/math] операций.
Таким образом, на построение списка [math]D_j[/math] будет потрачено [math]O(j)[/math] операций. Тогда время работы алгоритма составляет [math]O(n^2)[/math]. |
[math]\triangleleft[/math] |
См. также
Источники информации
- А. Ахо, Дж. Ульман. Теория синтакcического анализа, перевода и компиляции. Том 1. Синтакcический анализ. Издательство "Мир", Москва, 1978г., стр. 364-366