Алгоритм Эрли, доказательство оценки O(n^2) для однозначной грамматики — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 4: Строка 4:
 
На вход подается [[Контекстно-свободные грамматики, вывод, лево- и правосторонний вывод, дерево разбора|КС-грамматика]] <tex>G = (N, \Sigma, P, S)</tex> и строка <tex>w = a_1 a_2 \ldots a_n</tex> из <tex>\Sigma^*</tex>. Результатом работы алгоритма является [[Алгоритм Эрли#Определения|список разбора]] <tex>I_0, I_1, \ldots , I_n</tex> для строки <tex>w</tex>.
 
На вход подается [[Контекстно-свободные грамматики, вывод, лево- и правосторонний вывод, дерево разбора|КС-грамматика]] <tex>G = (N, \Sigma, P, S)</tex> и строка <tex>w = a_1 a_2 \ldots a_n</tex> из <tex>\Sigma^*</tex>. Результатом работы алгоритма является [[Алгоритм Эрли#Определения|список разбора]] <tex>I_0, I_1, \ldots , I_n</tex> для строки <tex>w</tex>.
  
'''Построение <tex>I_0</tex>:'''
+
Для простоты добавим новый стартовый вспомогательный нетерминал <tex>S'</tex> и правило <tex>S' \rightarrow S</tex>.
  
''Шаг 1.'' Для каждого правила <tex>S \rightarrow \alpha</tex> из <tex>P</tex>, включить <tex>[S \rightarrow \cdot \alpha, 0]</tex> в <tex>I_0</tex>.
+
<tex>I_0</tex> &cup;= <tex>[S' \rightarrow \cdot S, 0]</tex> # Правило (0) — инициализация
 +
useful_loop(0)
 +
 +
for i = 1..n
 +
    for <tex>[A \rightarrow \alpha \cdot a_{j} \beta, i] \in I_{j-1}</tex>
 +
        <tex>I_j</tex> &cup;= <tex>[A \rightarrow \alpha a_{j} \cdot \beta, i]</tex> # Правило (1)
 +
    useful_loop(j)
  
Выполнять шаги <tex>(2)</tex> и <tex>(3)</tex> до тех пор, пока в <tex>I_0</tex> можно включать новые ситуации.
+
function useful_loop(j):
 
+
    do
''Шаг 2.'' Если <tex>[B \rightarrow \gamma \cdot, 0] \in I_0</tex>, то для всех <tex>[A \rightarrow \alpha \cdot B \beta, 0] \in I_0</tex> включить в <tex>I_0</tex> ситуацию <tex>[A \rightarrow \alpha B \cdot \beta, 0]</tex>.
+
        for <tex>[B \rightarrow \eta \cdot , i] \in I_j</tex>
 
+
            for <tex>[A \rightarrow \alpha \cdot B \beta, k] \in I_{i}</tex>
''Шаг 3.'' Если <tex>[A \rightarrow \alpha \cdot B \beta, 0] \in I_0</tex>, то для всех правил из <tex>P</tex>  вида <tex>B \rightarrow \gamma</tex> включить в <tex>I_0</tex> ситуацию <tex>[B \rightarrow \cdot \gamma, 0]</tex>.
+
                <tex>I_j</tex> &cup;= <tex>[A \rightarrow \alpha B \cdot \beta, k]</tex> # Правило (2)
 
+
           
'''После того, как построены <tex>I_0, I_1, \ldots , I_{j-1}</tex>, строится <tex>I_j</tex>:'''
+
        for <tex>[B \rightarrow \alpha \cdot A \eta, k] \in I_j</tex>
 
+
            for <tex>\beta : (A \rightarrow \beta) \in P</tex>
''Шаг 4.'' Для каждой ситуации <tex>[B \rightarrow \alpha \cdot a_j \beta, i] \in I_{j-1}</tex> включить в <tex>I_j</tex> ситуацию <tex>[B \rightarrow \alpha a_j \cdot \beta, i]</tex>.
+
                <tex>I_j</tex> &cup;= <tex>[A \rightarrow \cdot \beta, j]</tex> # Правило (3)
 
+
    while на данной итерации какое-то множество изменилось
Выполнять шаги <tex>(5)</tex> и <tex>(6)</tex> до тех пор, пока в <tex>I_j</tex> можно включать новые ситуации.
 
 
 
''Шаг 5.'' Если <tex>[A \rightarrow \alpha \cdot, i] \in I_j</tex>, то для всех ситуаций <tex>[B \rightarrow \alpha \cdot A \beta, k] \in I_i</tex> включить <tex>[B \rightarrow \alpha A \cdot \beta, k]</tex> в <tex>I_j</tex>.
 
 
 
''Шаг 6.'' Если <tex>[A \rightarrow \alpha \cdot B \beta, i] \in I_j</tex>, то для всех правил <tex>B \rightarrow \gamma</tex> из <tex>P</tex> включить <tex>[B \rightarrow \cdot \gamma, j]</tex> в <tex>I_j</tex>.
 
  
 
==Время работы для однозначной грамматики==
 
==Время работы для однозначной грамматики==
Строка 38: Строка 39:
 
Пусть <tex>G = (N, \Sigma, P, S)</tex> {{---}} однозначная КС-грамматика и <tex>a_1 \dots a_n</tex> {{---}} цепочка из <tex>\Sigma^*</tex>. Тогда алгоритм Эрли пытается включить <tex>[A \rightarrow \alpha \cdot \beta, i]</tex> в <tex>I_j</tex> не более одного раза, если <tex>\alpha \ne \varepsilon</tex>.
 
Пусть <tex>G = (N, \Sigma, P, S)</tex> {{---}} однозначная КС-грамматика и <tex>a_1 \dots a_n</tex> {{---}} цепочка из <tex>\Sigma^*</tex>. Тогда алгоритм Эрли пытается включить <tex>[A \rightarrow \alpha \cdot \beta, i]</tex> в <tex>I_j</tex> не более одного раза, если <tex>\alpha \ne \varepsilon</tex>.
 
|proof=
 
|proof=
Ситуацию <tex>[A \rightarrow \alpha \cdot \beta, i]</tex> можно включить в <tex>I_j</tex> только на шагах <tex>(2)</tex>, <tex>(4)</tex>, или <tex>(5)</tex>. Если она включается на шаге <tex>(4)</tex>, то последний символ цепочки <tex>\alpha</tex> {{---}} терминал, а если на шагах <tex>(2)</tex> или <tex>(5)</tex>, то {{---}} нетерминал. В первом случае результат очевиден. Во втором случае допустим, что <tex>[A \rightarrow \alpha'B \cdot \beta, i]</tex> включается в <tex>I_j</tex>, когда рассматриваются две различные ситуации <tex>[B \rightarrow \gamma \cdot, k]</tex> и <tex>[B \rightarrow \delta \cdot, l]</tex>. Тогда ситуация <tex>[A \rightarrow \alpha' \cdot B\beta, i]</tex> должна оказаться одновременно в <tex>I_k</tex> и в <tex>I_l</tex>.
+
Ситуацию <tex>[A \rightarrow \alpha \cdot \beta, i]</tex> можно включить в <tex>I_j</tex> только по правилам <tex>(1)</tex> и <tex>(2)</tex>. Если она включается по правилу <tex>(1)</tex>, то последний символ цепочки <tex>\alpha</tex> {{---}} терминал, а если по правилу <tex>(2)</tex>, то {{---}} нетерминал. В первом случае результат очевиден. Во втором случае допустим, что <tex>[A \rightarrow \alpha'B \cdot \beta, i]</tex> включается в <tex>I_j</tex>, когда рассматриваются две различные ситуации <tex>[B \rightarrow \gamma \cdot, k]</tex> и <tex>[B \rightarrow \delta \cdot, l]</tex>. Тогда ситуация <tex>[A \rightarrow \alpha' \cdot B\beta, i]</tex> должна оказаться одновременно в <tex>I_k</tex> и в <tex>I_l</tex>.
 
#Пусть <tex>k \ne l</tex>. Тогда по [[Алгоритм Эрли#Корректность алгоритма|теореме]] существуют такие <tex>\theta_1, \theta_2, \theta_3</tex> и <tex>\theta_4</tex>, что <tex>S \Rightarrow^* \theta_1 A \theta_2 \Rightarrow \theta_1 \alpha' B \beta \theta_2 \Rightarrow^* a_1 \dots a_n</tex> и <tex>S \Rightarrow^* \theta_3 A \theta_4 \Rightarrow \theta_3 \alpha' B \beta \theta_4 \Rightarrow^* a_1 \dots a_n</tex>. Но в первом выводе <tex>\theta_1 \alpha' \Rightarrow^* a_1 \dots a_k</tex>, а во втором <tex>\theta_1 \alpha' \Rightarrow^* a_1 \dots a_l</tex>. Тогда для цепочки <tex>a_1 \dots a_n</tex> существуют два разных дерева вывода, в которых <tex>a_{i+1} \dots a_j</tex> выводится из <tex>\alpha' B</tex> двумя разными способами.
 
#Пусть <tex>k \ne l</tex>. Тогда по [[Алгоритм Эрли#Корректность алгоритма|теореме]] существуют такие <tex>\theta_1, \theta_2, \theta_3</tex> и <tex>\theta_4</tex>, что <tex>S \Rightarrow^* \theta_1 A \theta_2 \Rightarrow \theta_1 \alpha' B \beta \theta_2 \Rightarrow^* a_1 \dots a_n</tex> и <tex>S \Rightarrow^* \theta_3 A \theta_4 \Rightarrow \theta_3 \alpha' B \beta \theta_4 \Rightarrow^* a_1 \dots a_n</tex>. Но в первом выводе <tex>\theta_1 \alpha' \Rightarrow^* a_1 \dots a_k</tex>, а во втором <tex>\theta_1 \alpha' \Rightarrow^* a_1 \dots a_l</tex>. Тогда для цепочки <tex>a_1 \dots a_n</tex> существуют два разных дерева вывода, в которых <tex>a_{i+1} \dots a_j</tex> выводится из <tex>\alpha' B</tex> двумя разными способами.
 
#Пусть <tex>k = l</tex>. Тогда <tex>\gamma \ne \delta</tex>.  Тогда, так как <tex>[B \rightarrow \gamma \cdot, k] \in I_j</tex> и <tex>[B \rightarrow \delta \cdot, k] \in I_j</tex>, то <tex>\gamma \Rightarrow a_{k+1} \dots a_j</tex> и <tex>\delta \Rightarrow a_{k+1} \dots a_j</tex>, то есть <tex>a_{k+1} \dots a_j</tex> выводится двумя разными способами.
 
#Пусть <tex>k = l</tex>. Тогда <tex>\gamma \ne \delta</tex>.  Тогда, так как <tex>[B \rightarrow \gamma \cdot, k] \in I_j</tex> и <tex>[B \rightarrow \delta \cdot, k] \in I_j</tex>, то <tex>\gamma \Rightarrow a_{k+1} \dots a_j</tex> и <tex>\delta \Rightarrow a_{k+1} \dots a_j</tex>, то есть <tex>a_{k+1} \dots a_j</tex> выводится двумя разными способами.
Строка 51: Строка 52:
 
При построении <tex>I_0</tex> входная строка не учитывается, поэтому этот список можно построить за константное время.
 
При построении <tex>I_0</tex> входная строка не учитывается, поэтому этот список можно построить за константное время.
  
Рассмотрим <tex>I_j, \, j > 0</tex>. Рассмотрим шаги <tex>(4)</tex>, <tex>(5)</tex> и <tex>(6)</tex>.
+
Рассмотрим <tex>I_j, \, j > 0</tex>.
# На шаге <tex>(4)</tex> исследуется <tex>a_j</tex> и предыдущий список. Для каждой ситуации из <tex>I_{j-1}</tex> с символом <tex>a_j</tex>, расположенным справа от точки, в <tex>I_j</tex> включается некоторая ситуация.  Так как список в <tex>I_{j-1}</tex> можно найти за <tex>O(1)</tex> по символу <tex>a_j</tex>, то на включение каждой ситуации в <tex>I_j</tex> будет потрачено <tex>O(1)</tex> операций.
+
# При включении ситуации по правилу <tex>(1)</tex> исследуется <tex>a_j</tex> и предыдущий список. Для каждой ситуации из <tex>I_{j-1}</tex> с символом <tex>a_j</tex>, расположенным справа от точки, в <tex>I_j</tex> включается некоторая ситуация.  Так как список в <tex>I_{j-1}</tex> можно найти за <tex>O(1)</tex> по символу <tex>a_j</tex>, то на включение каждой ситуации в <tex>I_j</tex> будет потрачено <tex>O(1)</tex> операций.
#Если применяется шаг <tex>(5)</tex>, то в некотором списке <tex>I_k</tex> для <tex>k \le j</tex> надо просмотреть все ситуации, содержащие <tex>"\cdot B"</tex> для некоторого конкретного <tex>B</tex>. Для каждой такой ситуации в <tex>I_j</tex> включается другая ситуация, и это время относится не к рассматриваемой ситуации, а к включаемой. Кроме того, так как по второй лемме для каждой ситуации предпринимается только одна попытка включить ее в список, то не нужно тратить время на проверку того, что включаемая ситуация уже есть в списке.
+
#Если применяется правило <tex>(2)</tex>, то в некотором списке <tex>I_k</tex> для <tex>k \le j</tex> надо просмотреть все ситуации, содержащие <tex>"\cdot B"</tex> для некоторого конкретного <tex>B</tex>. Для каждой такой ситуации в <tex>I_j</tex> включается другая ситуация, и это время относится не к рассматриваемой ситуации, а к включаемой. Кроме того, так как по второй лемме для каждой ситуации предпринимается только одна попытка включить ее в список, то не нужно тратить время на проверку того, что включаемая ситуация уже есть в списке.
#Так как грамматика фиксирована, то на шаге <tex>(6)</tex> при рассмотрении любой ситуации количество включаемых ситуаций не превосходит некоторой константы, поэтому на рассматриваемую ситуацию будет потрачено <tex>O(1)</tex> операций.
+
#Так как грамматика фиксирована, то при применении правила <tex>(3)</tex> при рассмотрении любой ситуации количество включаемых ситуаций не превосходит некоторой константы, поэтому на рассматриваемую ситуацию будет потрачено <tex>O(1)</tex> операций.
 
Таким образом, на каждую ситуацию в каждом списке тратится <tex>O(1)</tex> операций. Тогда, учитывая лемму 1, получаем, что время работы алгоритма составляет <tex>O(n^2)</tex>.
 
Таким образом, на каждую ситуацию в каждом списке тратится <tex>O(1)</tex> операций. Тогда, учитывая лемму 1, получаем, что время работы алгоритма составляет <tex>O(n^2)</tex>.
 
}}
 
}}
 
==Литература==
 
==Литература==
 
*А. Ахо, Дж. Ульман. Теория синтакcического анализа, перевода и компиляции. Том 1. Синтакcический анализ.
 
*А. Ахо, Дж. Ульман. Теория синтакcического анализа, перевода и компиляции. Том 1. Синтакcический анализ.

Версия 01:31, 19 января 2012

Алгоритм

Приведем Алгоритм Эрли.

На вход подается КС-грамматика [math]G = (N, \Sigma, P, S)[/math] и строка [math]w = a_1 a_2 \ldots a_n[/math] из [math]\Sigma^*[/math]. Результатом работы алгоритма является список разбора [math]I_0, I_1, \ldots , I_n[/math] для строки [math]w[/math].

Для простоты добавим новый стартовый вспомогательный нетерминал [math]S'[/math] и правило [math]S' \rightarrow S[/math].

[math]I_0[/math] ∪= [math][S' \rightarrow \cdot S, 0][/math] # Правило (0) — инициализация
useful_loop(0)

for i = 1..n
    for [math][A \rightarrow \alpha \cdot a_{j} \beta, i] \in I_{j-1}[/math]
        [math]I_j[/math] ∪= [math][A \rightarrow \alpha a_{j} \cdot \beta, i][/math] # Правило (1)
    useful_loop(j)
function useful_loop(j):
    do
        for [math][B \rightarrow \eta \cdot , i] \in I_j[/math]
            for [math][A \rightarrow \alpha \cdot B \beta, k] \in I_{i}[/math]
                [math]I_j[/math] ∪= [math][A \rightarrow \alpha B \cdot \beta, k][/math] # Правило (2)
            
        for [math][B \rightarrow \alpha \cdot A \eta, k] \in I_j[/math]
            for [math]\beta : (A \rightarrow \beta) \in P[/math]
                [math]I_j[/math] ∪= [math][A \rightarrow \cdot \beta, j][/math] # Правило (3)
    while на данной итерации какое-то множество изменилось

Время работы для однозначной грамматики

Лемма (1):
[math]\forall\,j: 1 \le j \le n[/math] в списке [math]I_j[/math] находится [math]O(j)[/math] ситуаций.
Доказательство:
[math]\triangleright[/math]
Так как грамматика фиксирована, то [math]\forall i[/math] количество ситуаций вида [math][A \rightarrow \alpha \cdot \beta, i][/math] не больше некоторой константы. Таким образом, так как в [math]I_j[/math] находятся ситуации, у которых [math]0 \le i \le j[/math], то всего в [math]I_j[/math] будет [math]O(j)[/math] ситуаций.
[math]\triangleleft[/math]
Лемма (2):
Пусть [math]G = (N, \Sigma, P, S)[/math] — однозначная КС-грамматика и [math]a_1 \dots a_n[/math] — цепочка из [math]\Sigma^*[/math]. Тогда алгоритм Эрли пытается включить [math][A \rightarrow \alpha \cdot \beta, i][/math] в [math]I_j[/math] не более одного раза, если [math]\alpha \ne \varepsilon[/math].
Доказательство:
[math]\triangleright[/math]

Ситуацию [math][A \rightarrow \alpha \cdot \beta, i][/math] можно включить в [math]I_j[/math] только по правилам [math](1)[/math] и [math](2)[/math]. Если она включается по правилу [math](1)[/math], то последний символ цепочки [math]\alpha[/math] — терминал, а если по правилу [math](2)[/math], то — нетерминал. В первом случае результат очевиден. Во втором случае допустим, что [math][A \rightarrow \alpha'B \cdot \beta, i][/math] включается в [math]I_j[/math], когда рассматриваются две различные ситуации [math][B \rightarrow \gamma \cdot, k][/math] и [math][B \rightarrow \delta \cdot, l][/math]. Тогда ситуация [math][A \rightarrow \alpha' \cdot B\beta, i][/math] должна оказаться одновременно в [math]I_k[/math] и в [math]I_l[/math].

  1. Пусть [math]k \ne l[/math]. Тогда по теореме существуют такие [math]\theta_1, \theta_2, \theta_3[/math] и [math]\theta_4[/math], что [math]S \Rightarrow^* \theta_1 A \theta_2 \Rightarrow \theta_1 \alpha' B \beta \theta_2 \Rightarrow^* a_1 \dots a_n[/math] и [math]S \Rightarrow^* \theta_3 A \theta_4 \Rightarrow \theta_3 \alpha' B \beta \theta_4 \Rightarrow^* a_1 \dots a_n[/math]. Но в первом выводе [math]\theta_1 \alpha' \Rightarrow^* a_1 \dots a_k[/math], а во втором [math]\theta_1 \alpha' \Rightarrow^* a_1 \dots a_l[/math]. Тогда для цепочки [math]a_1 \dots a_n[/math] существуют два разных дерева вывода, в которых [math]a_{i+1} \dots a_j[/math] выводится из [math]\alpha' B[/math] двумя разными способами.
  2. Пусть [math]k = l[/math]. Тогда [math]\gamma \ne \delta[/math]. Тогда, так как [math][B \rightarrow \gamma \cdot, k] \in I_j[/math] и [math][B \rightarrow \delta \cdot, k] \in I_j[/math], то [math]\gamma \Rightarrow a_{k+1} \dots a_j[/math] и [math]\delta \Rightarrow a_{k+1} \dots a_j[/math], то есть [math]a_{k+1} \dots a_j[/math] выводится двумя разными способами.
[math]\triangleleft[/math]
Теорема:
Если входная грамматика однозначна, то время выполнения алгоритма Эрли для слова длины [math]n[/math] составляет [math]O(n^2)[/math].
Доказательство:
[math]\triangleright[/math]

Орагнизуем каждый список разбора [math]I_j[/math] таким образом, чтобы по любому символу [math]x \in \Sigma \cup N[/math], можно было за [math]O(1)[/math] получить список тех и только тех ситуаций, содержащихся в [math]I_j[/math], которые имеют вид [math][A \rightarrow \alpha \cdot x \beta, j][/math].

При построении [math]I_0[/math] входная строка не учитывается, поэтому этот список можно построить за константное время.

Рассмотрим [math]I_j, \, j \gt 0[/math].

  1. При включении ситуации по правилу [math](1)[/math] исследуется [math]a_j[/math] и предыдущий список. Для каждой ситуации из [math]I_{j-1}[/math] с символом [math]a_j[/math], расположенным справа от точки, в [math]I_j[/math] включается некоторая ситуация. Так как список в [math]I_{j-1}[/math] можно найти за [math]O(1)[/math] по символу [math]a_j[/math], то на включение каждой ситуации в [math]I_j[/math] будет потрачено [math]O(1)[/math] операций.
  2. Если применяется правило [math](2)[/math], то в некотором списке [math]I_k[/math] для [math]k \le j[/math] надо просмотреть все ситуации, содержащие [math]"\cdot B"[/math] для некоторого конкретного [math]B[/math]. Для каждой такой ситуации в [math]I_j[/math] включается другая ситуация, и это время относится не к рассматриваемой ситуации, а к включаемой. Кроме того, так как по второй лемме для каждой ситуации предпринимается только одна попытка включить ее в список, то не нужно тратить время на проверку того, что включаемая ситуация уже есть в списке.
  3. Так как грамматика фиксирована, то при применении правила [math](3)[/math] при рассмотрении любой ситуации количество включаемых ситуаций не превосходит некоторой константы, поэтому на рассматриваемую ситуацию будет потрачено [math]O(1)[/math] операций.
Таким образом, на каждую ситуацию в каждом списке тратится [math]O(1)[/math] операций. Тогда, учитывая лемму 1, получаем, что время работы алгоритма составляет [math]O(n^2)[/math].
[math]\triangleleft[/math]

Литература

  • А. Ахо, Дж. Ульман. Теория синтакcического анализа, перевода и компиляции. Том 1. Синтакcический анализ.