Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n)) — различия между версиями
Строка 14: | Строка 14: | ||
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний. | Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний. | ||
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. | Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. | ||
+ | |||
+ | Итеративно строим разбиение множества состояний следующим образом. | ||
+ | # Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний. | ||
+ | # Оба этих класса помещаются в очередь. | ||
+ | # Из очереди извлекается класс, далее именуемый как сплиттер. | ||
+ | # Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ. | ||
+ | # Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. | ||
+ | # Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь. | ||
+ | # Пока очередь не пуста, выполняем п.3 – п.6. | ||
===Псевдокод=== | ===Псевдокод=== | ||
Строка 45: | Строка 54: | ||
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> | :<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>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> | + | :<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> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> |
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/> | Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/> | ||
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/> | Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/> | ||
Строка 54: | Строка 63: | ||
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> | :<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>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> | + | :<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or |
− | :<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> | + | :<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> |
Из этого следует, что разбиение всех классов с помощью <tex>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>. | |
− | |||
− | |||
− | |||
− | |||
===Псевдокод=== | ===Псевдокод=== | ||
Строка 84: | Строка 89: | ||
<tex>W</tex>.pop(<tex>S</tex>) | <tex>W</tex>.pop(<tex>S</tex>) | ||
for all <tex>a \in \Sigma</tex> | for all <tex>a \in \Sigma</tex> | ||
− | for | + | for each <tex>R</tex> in <tex>P</tex> split by <tex>S</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> | Благодаря системе добавления классов состояний в очередь, каждое ребро будет рассмотрено не более чем <tex>\log{n}</tex> раз. А так как ребер у нас порядка <tex> |\Sigma| * n </tex> то получаем <tex> O(n\log{n})</tex> |
Версия 00:31, 15 февраля 2012
Содержание
Постановка задачи
Пусть дан автомат, распознающий определенный язык. Требуется найти эквивалентный автомат с наименьшим количеством состояний.
Минимизация ДКА
Если в ДКА существуют два эквивалентных состояния, то при их объединении мы получим эквивалентный ДКА, так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА.
Простой алгоритм
Определение: |
Класс | разбивает класс по символу на и , если
Если класс
может быть разбит по символу , то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа ). Если класс нельзя разбить, то он состоит из эквивалентных состояний. Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно.Итеративно строим разбиение множества состояний следующим образом.
- Первоначальное разбиение множества состояний — класс допускающих состояний и класс недопускающих состояний.
- Оба этих класса помещаются в очередь.
- Из очереди извлекается класс, далее именуемый как сплиттер.
- Перебираются все символы из алфавита , где — текущий символ.
- Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу переходят в сплиттер, а второй из всех оставшихся.
- Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.
- Пока очередь не пуста, выполняем п.3 – п.6.
Псевдокод
— множество состояний ДКА. — множество терминальных состояний. — очередь. — разбиение множества состояний ДКА. — класс состояний ДКА.
while not .isEmpty() .pop( ) for all for all in if and replace in with and .push( ) .push( )
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
Алгоритм Хопкрофта
Лемма: |
Класс и , тогда разбиение всех классов (текущее разбиение) по символу любыми двумя классами из эквивалентно разбиению всех классов с помощью по символу . |
Доказательство: |
Разобьем все классы с помощью и по символу , тогда для любого класса из текущего разбиения выполняется
А так как и то выполняется
Из этого следует, что разбиение всех классов с помощью
А так как и то выполняется
|
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь. Если класс
уже есть в очереди, то согласно лемме можно просто заменить его на и . Если класса нет в очереди, то согласно лемме в очередь можно добавить класс и любой из и , а так как для любого класса из текущего разбиения выполняется- or
то в очередь можно добавить только меньшее из
и .Псевдокод
— множество состояний ДКА. — множество терминальных состояний. — очередь. — разбиение множества состояний ДКА. — класс состояний ДКА.
if.push( ) else .push( ) while not .isEmpty() .pop( ) for all 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 (рус.)
- J. E. Hopcroft. An n log n algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.