Изменения

Перейти к: навигация, поиск
Нет описания правки
Итеративно строим разбиение множества состояний следующим образом.
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>).
# Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <<tex>\langle F,\ c\rangle</tex>> и <<tex>\langle Q \setminus F, c\rangle</tex>> помещаются в очередь.# Из очереди извлекается пара <<tex>\langle C,\ a\rangle</tex>>, <tex>C</tex> далее именуется как сплиттер.
# Каждый класс <tex>R</tex> текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер (<tex>R_1</tex>), а второй из всех оставшихся (<tex>R_2</tex>).
# Если <tex>R</tex> разбился на два непустых подкласса (т.е. <tex> R_1 \ne \emptyset \ \land \ R_2 \ne \emptyset </tex>).
## В разбиении <tex>P</tex> класс <tex>R</tex> заменяется на свои подклассы <tex>R_1</tex> и <tex>R_2</tex>.
## Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <<tex>\langle R_1, c\rangle</tex>> и <<tex>\langle R_2, c\rangle</tex>> помещаются в очередь.
# Пока очередь не пуста, выполняем п.3 – п.5.
*<tex>Q</tex> {{---}} множество состояний ДКА.
*<tex>F</tex> {{---}} множество терминальных состояний.
*<tex>\delta</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{- --}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>)*<tex>S</tex> {{---}} очередь пар <<tex>\langle C,\ a\rangle</tex>>.
*<tex>P</tex> {{---}} разбиение множества состояний ДКА.
*<tex>R</tex> {{---}} класс состояний ДКА.
<tex>\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex>:
<tex>\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
'''push''' <<tex>\langle F,\ c\rangle</tex>>, <<tex>\langle Q \setminus F,\ c\rangle</tex>> '''into''' <tex> \mathtt{S}</tex>
'''while''' <tex>\mathtt{S} \ne \varnothing</tex>
<<tex>\langle C,\ a\rangle</tex>> <tex>\leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex>
'''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>
<tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C,\ a)</tex>
'''replace''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
'''for''' <tex>c \in \Sigma</tex>
'''insert''' <<tex>\langle R_1,\ c\rangle</tex>> '''in''' <tex>\mathtt{S}</tex> '''insert''' <<tex>\langle R_2,\ c\rangle</tex>> '''in''' <tex>\mathtt{S}</tex>
'''return''' <tex>\mathtt{P}</tex>
===Время работы===
Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex> {{---}} алфавит. Это следует из того, что если пара <<tex>\langle C,\ a\rangle</tex>> попала в очередь, и класс <tex>C</tex> использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса <tex>C_1</tex> и <tex>C_2</tex>, причем можно гарантировать лишь следующее уменьшение размера: <tex>|C| \ge |C_i| + 1</tex>. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем <tex>O(n)</tex> раз. Учитывая, что ребер всего <tex>O(|\Sigma| \cdot n)</tex>, получаем указанную оценку.
== Алгоритм Хопкрофта==
|proof =
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R \ \land \ \delta(r, a) \in R_1\ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \in R \ \land \ \delta(r, a) \notin R_1\ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \notin R \ \land \ \delta(r, a) \notin R_1</tex>
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 \ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex>
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/>
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/>
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \ \delta(r, a) \notin R_2\ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \ \delta(r, a) \in R_2\ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \ \delta(r, a) \notin R_2</tex>
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R \ \lor</tex>:<tex> \ \lor \ \forall r \in B \,\,\, \delta(r, a) \notin R</tex>
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.
}}
После замены класса <tex>R</tex> в разбиении <tex>P</tex> на его подклассы <tex>R_1</tex> и <tex>R_2</tex>, как и раньше перебираем символы алфавита <tex>c \in \Sigma</tex>.
Если пара <<tex>\langle R,\ c\rangle</tex>> уже есть в очереди, то согласно лемме можно просто заменить её на пары <<tex>\langle R_1, c\rangle</tex>> и <<tex>\langle R_2, c\rangle</tex>>.
Если пары <<tex>\langle R,\ c\rangle</tex>> нет в очереди, то достаточно добавить любую из пар <<tex>\langle R_1, c\rangle</tex>> и <<tex>\langle R_2, c\rangle</tex>>. Это следует из следующих соображений: <tex>R</tex> может быть в разбиении только если в очередь были положены пары <<tex>\langle R,\ a\rangle</tex>> для <tex>\forall a \in \Sigma</tex>, а поскольку в очереди пары <<tex>\langle R,\ c\rangle</tex>> нет, то мы её уже успели рассмотреть, следовательно классы из разбиения <tex>P</tex> уже были разбиты по <<tex>\langle R,\ c\rangle</tex>>.
=== Реализация ===
<tex>\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2,\ c)</tex> {{---}} функция, которая добавляет одну из пар <<tex>\langle R_1, c\rangle</tex>>, <<tex>\langle R_2, c\rangle</tex>> в очередь S.
*<tex>Q</tex> {{---}} множество состояний ДКА.
*<tex>F</tex> {{---}} множество терминальных состояний.
*<tex>\delta</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> - состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>)
*<tex>S</tex> {{---}} очередь пар <<tex>\langle C,\ a\rangle</tex>>.
*<tex>P</tex> {{---}} разбиение множества состояний ДКА.
*<tex>R</tex> {{---}} класс состояний ДКА.
<tex>\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex>:
<tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
'''push''' <<tex>\langle F,\ c\rangle</tex>>, <<tex>\langle Q \setminus F,\ c\rangle</tex>> '''into''' <tex> \mathtt{S}</tex>
'''while''' <tex>\mathtt{S} \ne \varnothing</tex>
<<tex>\langle C,\ a\rangle</tex>> <tex>\leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex>
'''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>
<tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C,\ a)</tex>
'''return''' <tex>\mathtt{P}</tex>
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за <tex>T' </tex> (его нужно будет эффективно находить для каждой пары <<tex>\langle C,\ a\rangle</tex>>).
<tex>\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex>:
<tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
'''push''' <<tex>\langle F,\ c\rangle</tex>>, <<tex>\langle Q \setminus F,\ c\rangle</tex>> '''into''' <tex> \mathtt{S}</tex>
'''while''' <tex>\mathtt{S} \ne \varnothing</tex>
<<tex>\langle C,\ a\rangle</tex>> <tex>\leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex>
<tex>\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}</tex>
<tex>T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}</tex>
Каждая итерация цикла <tex> \mathrm{while} </tex> может быть выполнена за <tex> O(|Q| + |\mathtt{Inverse}|) </tex> для текущей пары <<tex>\langle C,\ a\rangle</tex>>. Покажем, как можно достичь этой оценки.
Классы разбиения <tex>P</tex> будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение - обычный вектор, индекс - номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за O(1)).
*<tex>\mathtt{Class}[r]</tex> {{---}} номер класса, которому принадлежит состояние <tex>r</tex>
*<tex>\mathtt{Card}[i]</tex> {{---}} размер класса <tex>i</tex>
*<tex>\mathtt{Queue}</tex> {{---}} очередь пар <<tex>\langle C,\ a\rangle</tex>>, где <tex>C</tex> {{---}} номер класса (сплиттера)*<tex>\mathtt{InQueue}</tex> {{---}} двумерный массив булеанов, <tex>\mathtt{InQueue}[C][a] == true</tex>, если <<tex>\langle C,\ a\rangle</tex>> находится в очереди <tex>\mathtt{Queue}</tex>
*<tex>\mathtt{Inv}[r][a]</tex> {{---}} массив состояний, из которых есть ребра по символу <tex>a</tex> в состояние <tex>r</tex> (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма)
*<tex>\mathtt{Twin}</tex> {{---}} массив, хранящий в <tex>\mathtt{Twin}[i]</tex> номер нового класса, образовавшегося при разбиении класса <tex>i</tex>.
<tex>\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex>:
<tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
'''for''' <tex>c \in \Sigma</tex>
'''push''' <<tex>\langle F,\ c\rangle</tex>>, <<tex>\langle Q \setminus F,\ c\rangle</tex>> '''into''' <tex> \mathtt{Queue}</tex>
<tex>\mathtt{InQueue}[F][c] \ \leftarrow \ true</tex>
<tex>\mathtt{InQueue}[Q \setminus F][c] \ \leftarrow \ true</tex>
'''while''' <tex>\mathtt{Queue} \ne \varnothing</tex>
<<tex>\langle C,\ a\rangle</tex>> <tex>\leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{Queue}</tex>
<tex>\mathtt{Involved} \leftarrow \varnothing</tex>
'''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
<tex>cnt2 \leftarrow \mathtt{Card}[R_2]</tex>
'''if''' <tex> \mathtt{InQueue}[R_1][c] == false </tex> '''and''' <tex> cnt1 \leqslant cnt2 </tex>
'''push''' <<tex>\langle R_1, c\rangle</tex>> '''to''' <tex>\mathtt{Queue}</tex>
<tex>\mathtt{InQueue}[R_1][c] \ \leftarrow \ true</tex>
'''else'''
'''push''' <<tex>\langle R_2, c\rangle</tex>> '''to''' <tex>\mathtt{Queue}</tex>
<tex>\mathtt{InQueue}[R_2][c] \ \leftarrow \ true</tex>
Количество итераций цикла <tex>\mathrm{while}</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>.
|proof =
Для доказательства этого утверждения достаточно показать, что количество пар <<tex>\langle C,\ a\rangle</tex>> добавленных в очередь <tex>S</tex> не превосходит <tex> 2 |\Sigma| |Q| </tex>, так как на каждой итерации мы извлекаем одну пару из очереди.
По [[#Лемма1 | лемме(1)]] количество классов не превосходит <tex>2 |Q| - 1</tex>. Пусть <tex>C</tex> элемент текущего разбиения. Тогда количество пар <<tex>\langle C,\ a\rangle</tex>>, <tex>\ a \in \Sigma</tex> не может быть больше <tex>|\Sigma|</tex>. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит <tex> 2 |\Sigma| |Q| </tex>.
}}
|id = Лемма3
|statement =
Пусть <tex>a \in \Sigma</tex> и <tex>p \in Q</tex>. Тогда количество пар <<tex>\langle C,\ a\rangle</tex>>, где <tex>p \in C</tex>, которые мы удалим из очереди, не превосходит <tex>\log_2(|Q|)</tex> для фиксированных <tex>a</tex> и <tex>p</tex>.
|proof =
Рассмотрим пару <<tex>\langle C,\ a\rangle</tex>>, где <tex>p \in C</tex>, которую мы удаляем из очереди. И пусть <<tex>\langle C',a\rangle</tex>> следующая пара, где <tex>p \in C'</tex> и которую мы удалим из очереди. Согласно нашему алгоритму класс <tex>C'</tex> мог появиться в очереди только после операции <tex>\mathtt{replace}</tex>. Но после первого же разбиения класса <tex>C</tex> на подклассы мы добавим в очередь пару <<tex>\langle C'', a\rangle</tex>>, где <tex>C''</tex> меньший из образовавшихся подклассов, то есть <tex>|C''| \leqslant |C| \ / \ 2</tex>. Так же заметим, что <tex>C' \subseteq C''</tex>, а следовательно <tex>|C'| \leqslant |C| \ / \ 2</tex>. Но тогда таких пар не может быть больше, чем <tex>\log_2(|Q|)</tex>.
}}
<tex>\sum |\mathtt{Inverse}|</tex> по всем итерациям цикла <tex>\mathrm{while}</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>.
|proof =
Пусть <tex>x, y \in Q</tex>, <tex>a \in \Sigma</tex> и <tex> \delta(x, a) = y</tex>. Зафиксируем эту тройку. Заметим, что количество раз, которое <tex>x</tex> встречается в <tex>\mathtt{Inverse}</tex> при условии, что <tex> \delta(x, a) = y</tex>, совпадает с числом удаленных из очереди пар <<tex>\langle C,\ a\rangle</tex>>, где <tex>y \in C</tex>. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит <tex>\log_2(|Q|)</tex>. Просуммировав по всем <tex> x \in Q </tex> и по всем <tex> a \in \Sigma</tex> мы получим утверждение леммы.
}}
<tex>\mathtt{pushSetsToQueue}</tex> реализуем так:
<tex>\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2,\ c)</tex>:
<tex>cnt1 \leftarrow \mathtt{ClassInv}[R_1][c]</tex>
<tex>cnt2 \leftarrow \mathtt{ClassInv}[R_2][c]</tex>
'''if''' <tex> \mathtt{InQueue}[R_1][c] == false </tex> '''and''' <tex> cnt1 \leqslant cnt2 </tex>
'''push''' <<tex>\langle R_1, c\rangle</tex>> '''to''' <tex>\mathtt{Queue}</tex>
'''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_1]</tex>
'''else'''
'''push''' <<tex>\langle R_2, c\rangle</tex>> '''to''' <tex>\mathtt{Queue}</tex>
'''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_2]</tex>
Кроме того, вместо [[Хеш-таблица | хэш-таблиц]] для хранения множеств (<tex>\mathtt{ClassInv}</tex>, разбиение <tex>P</tex>) можно использовать комбинацию из двусвязного списка и вектора (добавление/удаление через список, поиск через вектор). Что и используется в оригинальной статье.
 
== См. также ==
 
* [[Алгоритм Бржозовского]]
 
== Примечания ==
 
<references/>
== Источники информации ==
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]
== Примечания ==
 
<references/>
[[Категория: Теория формальных языков]]
[[Категория: Автоматы и регулярные языки]]
Анонимный участник

Навигация