Изменения

Перейти к: навигация, поиск
Простой алгоритм
Итеративно строим разбиение множества состояний следующим образом.
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ \mathtt{F, \ Q} \setminus \mathtt{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</tex> and <tex>\ \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>\mathtt{Q}</tex> {{---}} множество состояний ДКА.,*<tex>\mathtt{F}</tex> {{---}} множество терминальных состояний.,*<tex>\mathtt{\delta}</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{---}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>),*<tex>\mathtt{S}</tex> {{---}} очередь пар <tex>(\langle C, \ a)\rangle</tex>.,*<tex>\mathtt{P}</tex> {{---}} разбиение множества состояний ДКА.,*<tex>\mathtt{R}</tex> {{---}} класс состояний ДКА.  '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector''' <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> '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </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>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</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_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>)
'''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </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>(R_1, c)</tex> '''in''' <tex>\mathtt{S}</tex>
'''insert''' <tex>(R_2, c)</tex> '''in''' <tex>\mathtt{S}</tex>
Когда очередь <tex>S</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 geqslant |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</tex> and <tex> \ \land \ \delta(r, a) \in R_1\ \lor</tex> or :<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \ \land \ \delta(r, a) \notin R_1\ \lor</tex> or :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \ \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> or
:<tex> \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</tex> and <tex> \ \land \ \delta(r, a) \notin R_2\ \lor</tex> or :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \ \land \ \delta(r, a) \in R_2\ \lor</tex> or :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \ \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> or :<tex> \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>pushSetsToQueue(S, R_1, R_2, c)</tex> {{---}} функция, которая добавляет одно из <tex>(R_1, c)</tex>, <tex>(R_2, c)</tex> в очередь S.
*<tex>\mathtt{Q}</tex> {{---}} множество состояний ДКА.,*<tex>\mathtt{F}</tex> {{---}} множество терминальных состояний.,*<tex>\mathtt{\delta}</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{---}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>),*<tex>\mathtt{S}</tex> {{---}} очередь пар <tex>(\langle C, \ a)\rangle</tex>.,*<tex>\mathtt{P}</tex> {{---}} разбиение множества состояний ДКА.,*<tex>\mathtt{R}</tex> {{---}} класс состояний ДКА.  '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector''' <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> '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex> replace <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> with' <tex>R_1</tex> '''and''' <tex>R_2</tex> '''if''' <tex>\langle R,\ c \rangle</tex> '''in''' <tex> \mathtt{S}</tex> <font color=darkgreen>// смотрим, есть ли пара <tex>\langle R,\ c \rangle</tex> в очереди </font> remove <tex>\langle R, c \rangle</tex> '''from''' <tex>\mathtt{S}</tex> <font color=darkgreen>// заменяем её на пары <tex>\langle R_1, c \rangle</tex>, <tex>\langle R_2, c \rangle</tex> если пара есть </font> push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''else''' '''if''' <tex> |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| </tex> <font color=darkgreen>// вставляем любую иначе</font> push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''else''' push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''return''' <tex>\mathtt{P}</tex> 
<tex>\mathtt{P} \leftarrow \{ \mathtt{FПонятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, Q} \setminus \mathtt{F} \}</tex> из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за <tex>\mathtt{S} \leftarrow \varnothing </tex> '''for''T' <tex>c \in \Sigma</tex> <tex>pushSetsToQueue(S, F, Q \setminus F, c)</tex> '''while''' его нужно будет эффективно находить для каждой пары <tex>\mathtt{S} \ne \varnothing</tex> <tex>(langle C, a) \leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex> '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>) '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex> '''replace''' <tex>R</tex> '''in''' <tex>\mathtt{P}rangle</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex> '''for''' <tex>c \in \Sigma</tex> <tex>pushSetsToQueue(S, R_1, R_2, c)</tex>.
Понятно '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector''' <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> <font color=darkgreen>// находим классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за </font> '''for''' <tex>R</tex> '''in''' <tex>T'</tex> <font color=darkgreen>// перебираем только классы входящие в <tex>T' (его нужно будет эффективно находить для каждой пары </tex></font> <tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C, \ a)</tex>). '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex> replace <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> with <tex>R_1</tex> '''and''' <tex>R_2</tex> '''if''' <tex>\langle R,\ c \rangle</tex> '''in''' <tex> \mathtt{S}</tex> remove <tex>\langle R, c \rangle</tex> '''from''' <tex>\mathtt{S}</tex> push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''else''' '''if''' <tex> |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| </tex> push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''else''' push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex> '''return''' <tex>\mathtt{P}</tex>
<tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
<tex>\mathtt{S} \leftarrow \varnothing </tex>
'''for''' <tex>c \in \Sigma</tex>
<tex>pushSetsToQueue(S, F, Q \setminus F, c)</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''' <tex>R</tex> '''in''' <tex>T'</tex>
<tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>)
'''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </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>
<tex>pushSetsToQueue(S, R_1, R_2, c)</tex>
Каждая итерация цикла <tex> \mathrm{while} </tex> может быть выполнена за <tex> O(|Q| + |\mathtt{Inverse}|)\,</tex> для текущей пары <tex>\langle C,\ a \rangle</tex>. Покажем, как можно достичь этой оценки.
Каждая итерация цикла Классы разбиения <tex> \mathrm{while} P</tex> может быть выполнена за <tex> Oбудем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (|Q| + |\mathttсамо же разбиение {{---}} обычный вектор, индекс {{Inverse---}}|номер класса) </tex> для текущей пары . Это позволит нам эффективно переносить состояния из одного класса в другой (за <tex> O(C,a1)</tex>. Покажем, как можно достичь этой оценки).
Классы разбиения *<tex>P\mathtt{Class}[r]</tex> будем поддерживать с помощью множеств на хэш{{--таблицах (само же разбиение - обычный вектор}} номер класса, которому принадлежит состояние <tex>r</tex>, индекс *<tex>\mathtt{Queue}</tex> {{- --}} очередь пар <tex>\langle C,\ a \rangle</tex>, где <tex>C</tex> {{---}} номер класса(сплиттера). Это позволит нам эффективно переносить состояния ,*<tex>\mathtt{Inv}[r][a]</tex> {{---}} массив состояний, из одного класса которых есть ребра по символу <tex>a</tex> в другой состояние <tex>r</tex> (за O(1)мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма).
*Для обработки <tex>\mathtt{Class}[r]T'</tex> за <tex>O(|Q| + |\mathtt{{---Inverse}} номер класса|)\, которому принадлежит состояние <tex>r</tex>нам понадобится следующая структура:*<tex>\mathtt{CardInvolved}[i]</tex> {{---}} размер класса список из номеров классов, содержащихся во множестве <tex>iT'</tex>,*<tex>\mathtt{QueueCount}</tex> {{---}} очередь пар <tex>(C, a)</tex>целочисленный массив, где <tex>C</tex> - номер класса (сплиттера)*<tex>\mathtt{InQueueCount}[Ci]</tex> {{---}} множество на хэш-таблице, содержащее символ хранит количество состояний из класса <tex>ai</tex>, если которые содержатся в очереди содержится пара <tex>(C, a)\mathtt{Inverse}</tex>,*<tex>\mathtt{InvTwin}[r][a]</tex> {{---}} массив состояний, из которых есть ребра по символу хранящий в <tex>a\mathtt{Twin}[i]</tex> в состояние номер нового класса, образовавшегося при разбиении класса <tex>ri</tex> (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма).
Для обработки '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>T: '''vector''' <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex> '''for''' <tex>c \in \Sigma</tex> push <tex>\langle F,\ c \rangle</tex> за , <tex>O(|\langle Q| + |\setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{Queue}</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{InverseInv}|)[q][a]</tex> нам понадобится следующая структура:* <tex>i = \mathtt{CounterClass}[r]</tex> '''if''' <tex>\mathtt{Count}[i] == 0</tex> insert <tex>i</tex> '''into''' <tex>\mathtt{---Involved}</tex> <tex>\mathtt{Count} количество классов;[i]++</tex>* '''for''' <tex>i \in \mathtt{Involved}</tex> '''if''' <tex>\mathtt{Count}[i] < |\mathtt{---P}[i]|</tex> insert <tex>\{\} список из номеров классов, содержащихся во множестве </tex>T'''into'''<tex>\mathtt{P}</tex> <font color=darkgreen>// создадим пустой класс в разбиении <tex>\mathtt{P}</tex>;</font>* <tex>\mathtt{SizeTwin}[i] = |\mathtt{P}|</tex> <font color=darkgreen> //запишем в <tex>\mathtt{Twin[i]}</tex> индекс нового класса</font> '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{---Inv}[q][a]</tex> <tex>i = \mathtt{Class} целочисленный массив, где [r]</tex> <tex>j = \mathtt{SizeTwin}[i]</tex> хранит количество состояний из класса '''if''' <tex>j \neq 0</tex> remove <tex>r</tex> '''from''' <tex>\mathtt{P}[i]</tex> add <tex>r</tex>, которые содержатся в '''to''' <tex>\mathtt{P}[j]</tex> '''for''' <tex>i \in \mathtt{InverseInvolved}</tex>;* <tex>j = \mathtt{Twin}[i]</tex> '''if''' <tex> j \neq 0 </tex> '''if''' <tex>|\mathtt{P}[j]| > |\mathtt{P}[i]|</tex> <font color=darkgreen>// парный класс должен быть меньшего размера</font> <tex>\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])</tex> <font color=darkgreen>// swap за <tex>\mathtt{O(1)}</tex> {{---}} массивпросто переставить указатели</font> '''for''' <tex>r \in \mathtt{P}[j]</tex> <font color=darkgreen> // обновляем номера классов для вершин, хранящий в у которых они изменились</font> <tex>\mathtt{Class}[r] = j</tex> '''for''' <tex>c \in \Sigma</tex> push <tex>\langle j, c \rangle</tex> '''to''' <tex>\mathtt{Queue}</tex> <tex>\mathtt{Count}[i] = 0</tex> <tex>\mathtt{Twin}[i]= 0</tex> номер нового класса, образовавшегося при разбиении класса '''return''' <tex>i\mathtt{P}</tex>.
//Добавим <tex> F, Q \setminus F </tex> в <tex> \mathtt{Queue} </tex>, <tex>\mathtt{InQueue}</tex>
'''while''' <tex>\mathtt{Queue} \ne \varnothing</tex>
<tex>(C, a) \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>i = \mathtt{Class}[r]</tex>
'''if''' <tex>\mathtt{Size}[i] == 0</tex>
'''insert''' <tex>i</tex> '''in''' <tex>\mathtt{Involved}</tex>
<tex>\mathtt{Size}[i]++</tex>
'''for''' <tex> i \in \mathtt{Involved}</tex>
'''if''' <tex>\mathtt{Size}[i] < \mathtt{Card}[i]</tex>
<tex>\mathtt{Size}[i] = -1</tex> //Пометим сразу, т.к. в следующем цикле классы уже будут менятся
'''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
<tex>i = \mathtt{Class}[r]</tex>
'''if''' <tex>\mathtt{Size}[i] == -1</tex>
'''if''' <tex>\mathtt{Twin}[i] == 0</tex>
<tex>\mathtt{Counter}++</tex>
<tex>\mathtt{Twin[i]} = \mathtt{Counter}</tex>
'''move''' <tex>r</tex> '''from''' <tex>i</tex> '''to''' <tex>\mathtt{Twin}[i]</tex>
'''for''' <tex> j \in \mathtt{Involved}</tex>
'''if''' <tex> \mathtt{Twin}[j] \neq 0 </tex>
'''for''' <tex>c \in \Sigma</tex>
<tex>pushSetsToQueue(\mathtt{Queue}, j, \mathtt{Twin}[j], c)</tex>
<tex>\mathtt{Size}[j] = 0</tex>
<tex>\mathtt{Twin}[j] = 0</tex>
'''remove''' <tex>a</tex> '''from''' <tex>\mathtt{InQueue}[C]</tex>
Стоит отметить, что массивы <tex>\mathtt{SizeCount}, \ \mathtt{Twin}\,</tex> аллоцируются ровно один раз при инициализации алгоритма.
Осталось только реализовать <tex>pushSetsToQueue<Также стоит отметить, что собственно наличие/tex>отсутствие пары в очереди можно не проверять. Если для некоторого <tex>pushSetsToQueue(\mathtt{Queue}, R_1, R_2, c)</tex>: пара <tex>cnt1 \leftarrow langle i, c \mathtt{Card}[R_1]rangle</tex> уже была в очереди, то мы добавим её "вторую половинку" <tex>cnt2 \leftarrow langle \mathtt{CardTwin}[R_2i]</tex> '''if''' <tex> , c \lnot ( \mathtt{InQueue}[R_1] rangle</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>langle \mathtt{InQueueTwin}[R_1i]</tex> '''else''' '''push''' <tex>(R_2, c)</tex> '''to''' <tex>\mathtt{Queue}rangle</tex>. '''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_2]</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> мы получим утверждение леммы.
}}
*По [[#Лемма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>.
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем <tex>2 |Q| - 1</tex> классов, из чего следует, что количество операций <tex>\mathtt{replace}</tex> равно <tex>O(|\Sigma| |Q|)</tex>.
}}
== Сравнение с алгоритмом из оригинальной статьи Хопкрафта = Альтернативная реализация ===Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).
В [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CSВсе классы разбиения будем по-TR-71прежнему хранить в векторе хэш-190.pdf оригинальной статье] использовалась дополнительная структура, которую мы обозначим, как <tex>\mathtt{ClassInv}</tex>, в сетов <tex>\mathtt{ClassInvP}[C][a]</tex> будем хранить множество состояний, из которых есть ребро по символу <tex>a</tex> в состояние <tex>C</tex> (аналогично <tex>Inv</tex>, только для классов).
*<tex>\mathtt{ClassInvClass}[Cr][a] = </tex> {{---}} индекс класса в <tex>\mathtt{ s | P}</tex>, которому принадлежит состояние <tex>r</tex>,*<tex>\mathtt{ClassQueue}</tex> {{---}}[s] == очередь из пар <tex>\langle C ,\ a \rangle</tex> and ,*<tex> \delta^mathtt{Inv}[r][a]</tex> {{-1--} } массив состояний, из которых есть ребра по символу <tex>a</tex> в состояние <tex>r</tex> (sмы не меняем исходный автомат, aпотому может быть построен раз перед началом работы алгоритма) ,*<tex>\neq \emptyset \mathtt{Involved}</tex>{{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.
<tex>pushSetsToQueue\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex> реализуем так: <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex> '''for''' <tex>c \in \Sigma</tex> insert <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{Queue}</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} = \{\}</tex> '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex> <tex>i = \mathtt{Class}[r]</tex> '''if''' <tex>\mathtt{Involved}[i] == \varnothing</tex> <tex>\mathtt{Involved}[i] = \{\}</tex> add <tex>r</tex> '''to''' <tex>\mathtt{Involved}[i]</tex> '''for''' <tex> i \in \mathtt{Involved}</tex> <font color=darkgreen>//Перебираем ключи <tex>\mathtt{Involved}</tex></font> '''if''' <tex>|\mathtt{Involved}[i]| < |\mathtt{P}[i]|</tex> '''insert''' <tex>\{\}</tex> '''into''' <tex>\mathtt{P}</tex> <font color=darkgreen>//Создадим пустой класс в разбиении <tex>\mathtt{P}</tex></font> <tex>j = |\mathtt{P}|</tex> <font color=darkgreen>//Запишем в <tex>j</tex> индекс нового класса</font> '''for''' <tex>r</tex> '''in''' <tex>\mathtt{Involved}[i]</tex> remove <tex>r</tex> '''from''' <tex>\mathtt{P}[i]</tex> add <tex>r</tex> '''to''' <tex>\mathtt{P}[j]</tex> '''if''' <tex>|\mathtt{P}[j]| > |\mathtt{P}[i]|</tex> <font color=darkgreen>//Парный класс должен быть меньшего размера</font> <tex>\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])</tex> <font color=darkgreen>//swap за <tex>\mathtt{O(1)}</tex> {{---}} просто переставить указатели</font> '''for''' <tex>r \in \mathtt{P}[j]</tex> <font color=darkgreen>//Обновляем номера классов для вершин, у которых они изменились</font> <tex>\mathtt{Class}[r] = j</tex> '''for''' <tex>c \in \Sigma</tex> push <tex>\langle j, c \rangle</tex> '''into''' <tex>\mathtt{Queue}</tex> '''return''' <tex>\mathtt{P}</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>) можно использовать комбинацию из двусвязного списка и вектора (добавление/удаление через список, поиск через вектор). Что и используется в оригинальной статье. == Литература Источники информации ==
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
* ''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 Оригинальная статья Хопкрофта''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation
[[Категория: Теория формальных языков]]
[[Категория: Автоматы и регулярные языки]]
295
правок

Навигация