442
правки
Изменения
→Простой алгоритм
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.
== Минимизация ДКА ==
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА.
== Простой алгоритм ==
{{Определение
|definition =
Класс <tex>SC</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если # <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in SC</tex> # <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin SC</tex>
}}
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их можно различить любой строкой начинающейся с символа <tex>a</tex>различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно.
Итеративно строим разбиение множества состояний следующим образом.
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <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>\mathtt{Q}</tex> {{---}} множество состояний ДКА.,*<tex>\mathtt{F}</tex> {{---}} множество терминальных состояний.,*<tex>\mathtt{\delta}</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{---}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>),*<tex>W\mathtt{S}</tex> {{---}} очередь.пар <tex>\langle C,\ a \rangle</tex>,*<tex>\mathtt{P}</tex> {{---}} разбиение множества состояний ДКА.,*<tex>\mathtt{R}</tex> {{---}} класс состояний ДКА. '''function''' findEquivalenceClasses<tex>W (Q,\ F,\ \delta)</tex>: '''vector''' <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex> <tex>P \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 not ''' <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>W'''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>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.isEmpty ===Время работы===Время работы алгоритма оценивается как <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| \geqslant |C_i| + 1</tex>. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем <tex>O(n)</tex> раз. Учитывая, что ребер всего <tex>O(|\Sigma| \cdot n)</tex>, получаем указанную оценку. == Алгоритм Хопкрофта==Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за <tex> O(n^2) </tex>. {{Лемма|statement = Класс <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex>, тогда разбиение всех классов (текущее разбиение) по символу <tex>a</tex> любыми двумя классами из <tex>R, R_1, R_2</tex> эквивалентно разбиению всех классов с помощью <tex>R, R_1, R_2</tex> по символу <tex>a</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>\forall r \in B \,\,\, \delta(r, a) \in R \ \land \ \delta(r, a) \notin R_1 \ \lor</tex>:<tex>\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> \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>\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \ \delta(r, a) \in R_2 \ \lor</tex>:<tex>\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>\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>W\langle R,\ c \rangle</tex>.pop === Реализация === *<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 all ''' <tex>a 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 all ''' <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>T'</tex> (его нужно будет эффективно находить для каждой пары <tex>\langle C,\ a \rangle</tex>). '''function''' findEquivalenceClasses<tex>(Q,\ F,\cap \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{-1Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(Sr, 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 ,\setminus R_1C,\ a)</tex> '''if ''' <tex> |R_1| \ne 0\varnothing </tex> '''and ''' <tex>|R_2| \ne 0\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>W\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>W\mathtt{S}</tex>. '''else''' push(<tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>)Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс не может быть разбит. '''return''' <tex>\mathtt{P}</tex>
Каждая итерация цикла <tex> \mathrm{while} </tex> может быть выполнена за <tex> O(|Q| + |\mathtt{Inverse}|)\,</tex> для текущей пары <tex>\langle C,\ a \rangle</tex>. Покажем, как можно достичь этой оценки.
Классы разбиения <tex>P</tex> будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за <tex>O(1)</tex>).
===Псевдокод===<tex>Q</tex> {{---}} множество состояний ДКА.<tex>F</tex> {{---}} множество терминальных состояний.<tex>W</tex> {{---}} очередь.<tex>P</tex> {{---}} разбиение множества состояний ДКА.<tex>R</tex> {{---}} класс состояний ДКА. if <tex> |F| \le |Q \setminus F|</tex> <tex>W</tex>.push(<tex>F</tex>) else <tex>W</tex>.push(<tex>Q \setminus F</tex>) <tex>P \leftarrow \{ F, Q \setminus F \}</tex> while not <tex>W</tex>.isEmpty() <tex>W</tex>.pop(<tex>S</tex>) for all <tex>a \in \Sigma</tex> for all <tex>R</tex> in <tex>P</tex> <tex>R_1 = R \cap \delta^{-1} (S, a) </tex> <tex>R_2 = R \setminus R_1</tex> if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex> replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex> if <tex>R</tex> in <tex>W</tex> replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex> else if <tex> |R_1| \le |R_2|</tex> <tex>W</tex>.push(<tex>R_1</tex>) else <tex>W</tex>См.push(<tex>R_2</tex>)также =Корректность алгоритма=
* [[Алгоритм Бржозовского]]
=Время работы алгоритма=Благодаря системе добавления классов состояний в очередь, каждое ребро будет рассмотрено не более чем <tex>\log{n}</tex> раз. А так как ребер у нас порядка <tex> |\Sigma| * n </tex> то получаем <tex> O(n\log{n})</tex>Источники информации = Литература =
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
* ''JD. EGries. '' 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 n log n O(nlogn) algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.automation]
[[Категория: Теория формальных языков]]
[[Категория: Автоматы и регулярные языки]]