Изменения
Тикет 2-17
Итеративно строим разбиение множества состояний следующим образом.
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q } \setminus \mathtt{F} \}</tex>).
# Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>(F, c)</tex> и <tex>(Q \setminus F, c)</tex> помещаются в очередь.
# Из очереди извлекается пара <tex>(C, a)</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</tex> and <tex>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>(R_1, c)</tex> и <tex>(R_2, а подклассы добавляются c)</tex> помещаются в очередь.
# Пока очередь не пуста, выполняем п.3 – п.5.
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
<tex>R</tex> {{---}} класс состояний ДКА.
<tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''insert''' <tex>(\mathtt{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex>
'''insert''' <tex>(\mathtt{Q} \setminus \mathtt{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex>
'''while''' <tex> \mathtt{S} \ne \varnothing </tex> <tex>(C, a) \leftarrow</tex> '''pop'''('''from''' <tex>\mathtt{S}</tex>)
'''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>
<tex>R_1 = R , R_2 \cap \delta^{-1} (C, a) leftarrow </tex> '''split'''(<tex>R_2 = R \setminus R_1, C, a</tex>)
'''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex>
Когда очередь <tex>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
== Алгоритм Хопкрофта==
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за <tex> O(n^2) </tex>.
{{Лемма
}}
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы пары в очередь.После замены класса <tex>R</tex> в разбиении <tex>P</tex> на его подклассы <tex>R_1</tex> и <tex>R_2</tex>, как и раньше перебираем символы алфавита <tex>c \in \Sigma</tex>. Если класс пара <tex>(R, c)</tex> уже есть в очереди, то согласно лемме можно просто заменить его её на пары <tex>(R_1, c)</tex> и <tex>(R_2, c)</tex>. Если класса пары <tex>(R, c)</tex> нет в очереди, то согласно лемме в очередь можно достаточно добавить класс любую из пар <tex>R(R_1, c)</tex> и любой из <tex>R_1(R_2, c)</tex> и . Это следует из следующих соображений: <tex>R_2R</tex>, а так как для любого класса может быть в разбиении только если в очередь были положены пары <tex>B(R, a)</tex> из текущего разбиения выполняется :для <tex>\forall r a \in B \Sigma</tex>, а поскольку в очереди пары <tex>(R,\c)</tex> нет,\то мы её уже успели рассмотреть, \deltaследовательно классы из разбиения <tex>P</tex> уже были разбиты по <tex>(rR, ac) \in R </tex> or . === Реализация ===:<tex> \forall r \in B \pushSetsToQueue(S,\R_1,\, \delta(rR_2, ac) \notin R</tex> то в очередь можно добавить только меньшее {{---}} функция, которая добавляет одно из <tex>(R_1, c)</tex> и , <tex>(R_2, c)</tex>в очередь S.
<tex>Q</tex> {{---}} множество состояний ДКА.
<tex>F</tex> {{---}} множество терминальных состояний.
<tex>S</tex> {{---}} очередь из пар <tex>(C, a)</tex>.
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
<tex>R</tex> {{---}} класс состояний ДКА.
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
'''while''' <tex>\mathtt{S} \ne \varnothing</tex>
<tex>(C, a) \leftarrow</tex> '''pop'''('''from''' <tex>\mathtt{S}</tex>) <tex>T \leftarrow \{R \ | \ R \in \mathtt{P}, \ R</tex> splits by <tex>(C, a) \}</tex> '''for each''' <tex>R</tex> '''in''' <tex>T\mathtt{P}</tex> <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>) '''replace''' <tex>R</tex> '''inif''' <tex>R_1 \ne \mathtt{P}</tex> '''with''' <tex>R_1varnothing </tex> '''and''' <tex>R_2</tex> '''for''' <tex>c \in ne \Sigmavarnothing </tex> '''ifreplace''' <tex>(R, c)</tex> '''in''' <tex>\mathtt{SP}</tex> '''replacewith''' <tex> (R, c)R_1</tex> '''inand''' <tex>\mathtt{S}R_2</tex> '''withfor''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)\in \Sigma</tex> '''else''' '''insert''' <tex>pushSetsToQueue(\mathtt{min}(S, R_1, R_2), c)</tex> '''in''' <tex>\mathtt{S}</tex>
<tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
'''while''' <tex>\mathtt{S} \ne \varnothing</tex>
<tex>(C, a) \leftarrow</tex> '''pop'''('''from''' <tex>\mathtt{S}</tex>)
<tex>\mathtt{Inverse} \leftarrow \{r \ | \ r \in \mathtt{Q}, \ \delta(r, a) \in C\}</tex>
<tex>T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}</tex>
'''for each''' <tex>R</tex> '''in''' <tex>T'</tex> '''if''' <tex>R</tex> splits by <tex>(C, a)</tex> <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>) '''replace''' <tex>R</tex> '''inif''' <tex>R_1 \ne \mathtt{P}</tex> '''with''' <tex>R_1varnothing </tex> '''and''' <tex>R_2</tex> '''for''' <tex>c \in ne \Sigmavarnothing </tex> '''ifreplace''' <tex>(R, c)</tex> '''in''' <tex>\mathtt{SP}</tex> '''replacewith''' <tex>(R, c)R_1</tex> '''inand''' <tex>SR_2</tex> '''withfor''' <tex>(R_1, c)\in \Sigma</tex> '''and''' <tex>pushSetsToQueue(R_2S, c)</tex> '''else''' '''insert''' <tex>(\mathtt{min}(R_1, R_2), c)</tex> '''in''' <tex>\mathtt{S}</tex>
*<tex>\mathtt{Counter}</tex> {{---}} количество классов;
*<tex>\mathtt{Involved}</tex> {{---}} список из номеров классов, содержащихся во множестве <tex>T'</tex>;
*<tex>\mathtt{Twin}</tex> {{---}} массив, хранящий в <tex>\mathtt{Twin}[i]</tex> номер нового класса, образовавшегося при разбиении класса <tex>i</tex>.
===Время работы===
|id = Лемма2
|statement =
Количество итераций цикла <tex>\mathrm{while}</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>.
|proof =
Для доказательства этого утверждения достаточно показать, что количество пар <tex>(C,a)</tex> добавленных в очередь <tex>S</tex> не превосходит <tex> 2 |\Sigma| |Q| </tex>, так как на каждой итерации мы извлекаем одну пару из очереди.
|id = Лемма4
|statement =
<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>(C, a)</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{Inv}</tex> занимает <tex>O(|\Sigma| |Q|)</tex> времени.
*По [[#Лемма2 | второй лемме]] количество итераций цикла <tex>\mathrm{while}</tex> не превосходит <tex>O(|\Sigma| |Q|)</tex>.
*Операции с множеством <tex>T'</tex> и разбиение классов на подклассы требуют <tex>O(\sum(|\mathtt{Inverse}|))</tex> времени. Но по [[#Лемма4 | лемме(4)]] <tex>\sum(|\mathtt{Inverse}|)</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>, то есть данная часть алгоритма выполняется за <tex>O(|\Sigma| |Q| \log_2(|Q|))</tex>.
Итого, получается, что время работы алгоритма Хопкрофта не превышает <tex> O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))</tex>.
}}
== Сравнение с алгоритмом из оригинальной статьи Хопкрафта ==
В [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf оригинальной статье] использовалась дополнительная структура, которую мы обозначим, как <tex>\mathtt{ClassInv}</tex>, в <tex>\mathtt{ClassInv}[C][a]</tex> будем хранить множество состояний, из которых есть ребро по символу <tex>a</tex> в состояние <tex>C</tex> (аналогично <tex>Inv</tex>, только для классов).
<tex>\mathtt{ClassInv}[C][a] = \{ s | \mathtt{Class}[s] == C </tex> and <tex> \delta^{-1} (s, a) \neq \emptyset \}</tex>
<tex>pushSetsToQueue</tex> реализуем так:
<tex>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> \lnot ( \mathtt{InQueue}[R_1] </tex> '''contains''' <tex> c) </tex> '''and''' <tex> cnt1 <= cnt2 </tex>
'''push''' <tex>(R_1, c)</tex> '''to''' <tex>\mathtt{Queue}</tex>
'''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_1]</tex>
'''else'''
'''push''' <tex>(R_2, c)</tex> '''to''' <tex>\mathtt{Queue}</tex>
'''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_2]</tex>
Циклы
'''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
<tex>(...)</tex>
реализуются
'''for''' <tex>q \in \mathtt{ClassInv}[C][a]</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
<tex>(...)</tex>'
Тогда время работы внутреннего цикла можно будет оценить как <tex>O(|\mathtt{ClassInv}[C][a]| + |\mathtt{Inverse}|)</tex>. А реализация <tex>pushSetsToQueue</tex> выбирает множество, на котором <tex>O(|\mathtt{ClassInv}[C][a]|)</tex> будет меньшим.
Кроме того, вместо хэш-таблиц для хранения множеств (<tex>\mathtt{ClassInv}</tex>, разбиение <tex>P</tex>) можно использовать комбинацию из двусвязного списка и вектора (добавление/удаление через список, поиск через вектор). Что и используется в оригинальной статье.
== Литература ==
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf Оригинальная статья Хопкрофта]
[[Категория: Теория формальных языков]]
[[Категория: Автоматы и регулярные языки]]