|
|
(не показана 41 промежуточная версия 1 участника) |
Строка 1: |
Строка 1: |
− | Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.
| + | #перенаправление [[Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))]] |
− | | |
− | == Минимизация ДКА ==
| |
− | Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА.
| |
− | | |
− | == Простой алгоритм ==
| |
− | {{Определение
| |
− | |definition =
| |
− | Класс <tex>S</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 S</tex>
| |
− | # <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex>
| |
− | }}
| |
− | Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.
| |
− | Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно.
| |
− | | |
− | Итеративно строим разбиение множества состояний следующим образом.
| |
− | # Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний <tex>Q \setminus F</tex>.
| |
− | # Перебираются символы алфавита <tex>a \in \Sigma</tex>, все пары <tex>(F, a)</tex> и <tex>(Q \setminus F, a)</tex> помещаются в очередь.
| |
− | # Из очереди извлекается пара <tex>(S, a)</tex>, <tex>S</tex> далее именуется как сплиттер.
| |
− | # Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся.
| |
− | # Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.
| |
− | # Пока очередь не пуста, выполняем п.3 – п.5.
| |
− | | |
− | ===Псевдокод===
| |
− | <tex>Q</tex> {{---}} множество состояний ДКА.
| |
− | <tex>F</tex> {{---}} множество терминальных состояний.
| |
− | <tex>W</tex> {{---}} очередь.
| |
− | <tex>P</tex> {{---}} разбиение множества состояний ДКА.
| |
− | <tex>R</tex> {{---}} класс состояний ДКА.
| |
− | <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
| |
− | <tex>W \leftarrow \{ \}</tex>
| |
− | '''for all''' <tex>a \in \Sigma</tex>
| |
− | <tex>W</tex>'''.push'''(<tex>F, a</tex>)
| |
− | <tex>W</tex>'''.push'''(<tex>Q \setminus F, a</tex>)
| |
− | '''while not''' <tex>W</tex>'''.isEmpty()'''
| |
− | <tex>W</tex>'''.pop'''(<tex>S, a</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>
| |
− | '''for all''' <tex> c \in \Sigma </tex>
| |
− | <tex>W</tex>'''.push'''(<tex>R_1, c</tex>)
| |
− | <tex>W</tex>'''.push'''(<tex>R_2, c</tex>)
| |
− | Когда очередь станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
| |
− | | |
− | ===Время работы===
| |
− | Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что если пара <tex>(S, a)</tex> попала в очередь, и класс <tex>S</tex> использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса <tex>S_1</tex> и <tex>S_2</tex>, причем можно гарантировать лишь следующее уменьшение размера: <tex>|S| \ge |S_i| + 1</tex>. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем <tex>O(n)</tex> раз. Учитывая, что ребер всего <tex>O(|\Sigma| \cdot n)</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</tex> and <tex> \delta(r, a) \in R_1</tex> or
| |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or
| |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \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 </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> \delta(r, a) \notin R_2</tex> or
| |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or
| |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \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 </tex> or
| |
− | :<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex>
| |
− | Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.
| |
− | }}
| |
− | | |
− | Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.
| |
− | Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>.
| |
− | Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется
| |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or
| |
− | :<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex>
| |
− | то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.
| |
− | | |
− | ===Псевдокод===
| |
− | <tex>Q</tex> {{---}} множество состояний ДКА.
| |
− | <tex>F</tex> {{---}} множество терминальных состояний.
| |
− | <tex>W</tex> {{---}} очередь.
| |
− | <tex>P</tex> {{---}} разбиение множества состояний ДКА.
| |
− | <tex>R</tex> {{---}} класс состояний ДКА.
| |
− | <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
| |
− | <tex>W \leftarrow \{ \}</tex>
| |
− | '''for all''' <tex>a \in \Sigma</tex>
| |
− | <tex>W</tex>'''.push'''('''min'''(<tex>F, Q \setminus F</tex>), <tex>a</tex>)
| |
− | '''while not''' <tex>W</tex>'''.isEmpty()'''
| |
− | <tex>W</tex>'''.pop'''(<tex>S, a</tex>)
| |
− | '''for each''' <tex>R</tex> '''in''' <tex>P</tex> '''split by''' <tex>(S, a)</tex>
| |
− | '''replace''' <tex>R</tex> '''in''' <tex>P</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
| |
− | '''if''' (<tex>R, a</tex>) '''in''' <tex>W</tex>
| |
− | '''replace''' (<tex>R, a</tex>) '''in''' <tex>W</tex> '''with''' (<tex>R_1, a</tex>) '''and''' (<tex>R_2, a</tex>)
| |
− | '''else'''
| |
− | '''if''' <tex> |R_1| \le |R_2|</tex>
| |
− | <tex>W</tex>'''.push'''(<tex>R_1, a</tex>)
| |
− | '''else'''
| |
− | <tex>W</tex>'''.push'''(<tex>R_2, a</tex>)
| |
− | | |
− | ===Время работы===
| |
− | Время работы модифицированного алгоритма оценивается как <tex>O(|\Sigma| \cdot n\log{n})</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. В данном случае при последующем разбиении в очередь будет добавлен класс <tex>S_1</tex>, причем <tex>|S| \ge 2|S_1|</tex>. Каждый переход в автомате будет просмотрен не более, чем <tex>O(\log{n})</tex> раз, ребер всего <tex>O(|\Sigma| \cdot n)</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.
| |
− | | |
− | [[Категория: Теория формальных языков]]
| |
− | [[Категория: Автоматы и регулярные языки]]
| |