Алгоритм Хопкрофта — различия между версиями
Warrior (обсуждение | вклад) (Новая страница: «Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_со...») |
Warrior (обсуждение | вклад) (→Простой алгоритм) |
||
Строка 7: | Строка 7: | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | Класс <tex> | + | Класс <tex>C</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 | + | # <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in C</tex> |
− | # <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin | + | # <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin C</tex> |
}} | }} | ||
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний. | Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний. | ||
Строка 16: | Строка 16: | ||
Итеративно строим разбиение множества состояний следующим образом. | Итеративно строим разбиение множества состояний следующим образом. | ||
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний <tex>Q \setminus F</tex>. | # Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний <tex>Q \setminus F</tex>. | ||
− | # Перебираются символы алфавита <tex> | + | # Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>(F, c)</tex> и <tex>(Q \setminus F, c)</tex> помещаются в очередь. |
− | # Из очереди извлекается пара <tex>( | + | # Из очереди извлекается пара <tex>(C, a)</tex>, <tex>C</tex> далее именуется как сплиттер. |
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. | # Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. | ||
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь. | # Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь. | ||
Строка 25: | Строка 25: | ||
<tex>Q</tex> {{---}} множество состояний ДКА. | <tex>Q</tex> {{---}} множество состояний ДКА. | ||
<tex>F</tex> {{---}} множество терминальных состояний. | <tex>F</tex> {{---}} множество терминальных состояний. | ||
− | <tex> | + | <tex>S</tex> {{---}} очередь. |
<tex>P</tex> {{---}} разбиение множества состояний ДКА. | <tex>P</tex> {{---}} разбиение множества состояний ДКА. | ||
<tex>R</tex> {{---}} класс состояний ДКА. | <tex>R</tex> {{---}} класс состояний ДКА. | ||
<tex>P \leftarrow \{ F, Q \setminus F \}</tex> | <tex>P \leftarrow \{ F, Q \setminus F \}</tex> | ||
− | <tex> | + | <tex>S \leftarrow \varnothing </tex> |
− | '''for all''' <tex> | + | '''for all''' <tex>c \in \Sigma</tex> |
− | <tex> | + | <tex>S</tex>'''.push'''(<tex>F, c</tex>) |
− | <tex> | + | <tex>S</tex>'''.push'''(<tex>Q \setminus F, c</tex>) |
− | '''while not''' <tex> | + | '''while not''' <tex>S</tex>'''.isEmpty'''() |
− | <tex> | + | <tex>(C, a)</tex> <tex> \leftarrow </tex> <tex>S</tex>'''.pop'''() |
'''for all''' <tex>R</tex> '''in''' <tex>P</tex> | '''for all''' <tex>R</tex> '''in''' <tex>P</tex> | ||
− | <tex>R_1 = R \cap \delta^{-1} ( | + | <tex>R_1 = R \cap \delta^{-1} (C, a) </tex> |
<tex>R_2 = R \setminus R_1</tex> | <tex>R_2 = R \setminus R_1</tex> | ||
− | '''if''' <tex> | + | '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex> |
'''replace''' <tex>R</tex> '''in''' <tex>P</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</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> | '''for all''' <tex> c \in \Sigma </tex> | ||
− | <tex> | + | <tex>S</tex>'''.push'''(<tex>R_1, c</tex>) |
− | <tex> | + | <tex>S</tex>'''.push'''(<tex>R_2, c</tex>) |
Когда очередь станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить. | Когда очередь станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить. | ||
===Время работы=== | ===Время работы=== | ||
− | Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что если пара <tex>( | + | Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что если пара <tex>(C, a)</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>, получаем указанную оценку. |
== Алгоритм Хопкрофта== | == Алгоритм Хопкрофта== |
Версия 21:33, 6 ноября 2013
Пусть дан автомат, распознающий определенный язык. Требуется найти эквивалентный автомат с наименьшим количеством состояний.
Содержание
Минимизация ДКА
Если в ДКА существуют два эквивалентных состояния, то при их объединении мы получим эквивалентный ДКА, так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА.
Простой алгоритм
Определение: |
Класс | разбивает класс по символу на и , если
Если класс
может быть разбит по символу , то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний. Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно.Итеративно строим разбиение множества состояний следующим образом.
- Первоначальное разбиение множества состояний — класс допускающих состояний и класс недопускающих состояний .
- Перебираются символы алфавита , все пары и помещаются в очередь.
- Из очереди извлекается пара , далее именуется как сплиттер.
- Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу переходят в сплиттер, а второй из всех оставшихся.
- Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.
- Пока очередь не пуста, выполняем п.3 – п.5.
Псевдокод
— множество состояний ДКА. — множество терминальных состояний. — очередь. — разбиение множества состояний ДКА. — класс состояний ДКА.
for all .push( ) .push( ) while not .isEmpty() .pop() for all in if and replace in with and for all .push( ) .push( )
Когда очередь станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
Время работы
Время работы алгоритма оценивается как
, где — количество состояний ДКА, а — алфавит. Это следует из того, что если пара попала в очередь, и класс использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса и , причем можно гарантировать лишь следующее уменьшение размера: . Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем раз. Учитывая, что ребер всего , получаем указанную оценку.Алгоритм Хопкрофта
Лемма: |
Класс и , тогда разбиение всех классов (текущее разбиение) по символу любыми двумя классами из эквивалентно разбиению всех классов с помощью по символу . |
Доказательство: |
Разобьем все классы с помощью и по символу , тогда для любого класса из текущего разбиения выполняется
А так как и то выполняется
Из этого следует, что разбиение всех классов с помощью
А так как и то выполняется
|
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь. Если класс
уже есть в очереди, то согласно лемме можно просто заменить его на и . Если класса нет в очереди, то согласно лемме в очередь можно добавить класс и любой из и , а так как для любого класса из текущего разбиения выполняется- or
то в очередь можно добавить только меньшее из
и .Псевдокод
— множество состояний ДКА. — множество терминальных состояний. — очередь. — разбиение множества состояний ДКА. — класс состояний ДКА.
for all .push(min( ), ) while not .isEmpty() .pop( ) for each in split by replace in with and if ( ) in replace ( ) in with ( ) and ( ) else if .push( ) else .push( )
Время работы
Время работы модифицированного алгоритма оценивается как
, где — количество состояний ДКА, а — алфавит. В данном случае при последующем разбиении в очередь будет добавлен класс , причем . Каждый переход в автомате будет просмотрен не более, чем раз, ребер всего , отсюда указанная оценка.Литература
- Хопкрофт Д., Мотвани Р., Ульман Д. Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
- D. Gries. Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.