http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&user=178.178.21.14&feedformat=atomВикиконспекты - Вклад участника [ru]2024-03-28T09:01:30ZВклад участникаMediaWiki 1.30.0http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18092Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-15T08:16:27Z<p>178.178.21.14: /* Простой алгоритм */</p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Оба этих класса помещаются в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.<br />
# Пока очередь не пуста, выполняем п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.<br />
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>. <br />
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for each <tex>R</tex> in <tex>P</tex> split by <tex>S</tex> <br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Время работы алгоритма=<br />
Время работы алгоритма равно <tex>O(|\Sigma| * n\log{n})</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что каждое из ребер, а их порядка <tex> |\Sigma| * n </tex>, участвует не более чем в <tex> \log{n}</tex> разбиениях.<br />
<br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18090Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T23:46:05Z<p>178.178.21.14: /* Время работы алгоритма */</p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Оба этих класса помещаются в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.<br />
# Пока очередь не пуста, выполняем п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.<br />
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>. <br />
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for each <tex>R</tex> in <tex>P</tex> split by <tex>S</tex> <br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Время работы алгоритма=<br />
Время работы алгоритма равно <tex>O(|\Sigma| * n\log{n})</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что каждое из ребер, а их порядка <tex> |\Sigma| * n </tex>, участвует не более чем в <tex> \log{n}</tex> разбиениях.<br />
<br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18089Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T23:43:20Z<p>178.178.21.14: /* Литература */</p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Оба этих класса помещаются в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.<br />
# Пока очередь не пуста, выполняем п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.<br />
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>. <br />
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for each <tex>R</tex> in <tex>P</tex> split by <tex>S</tex> <br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Время работы алгоритма=<br />
Время работы алгоритма равно O(|\Sigma| * n\log{n})</tex>, где <tex> n </tex> {---} количество состояний ДКА, а <tex> \Sigma </tex> {---} алфавит. Это следует из того, что каждое из ребер, а их порядка <tex> |\Sigma| * n </tex>, участвует не более чем в <tex> log{n}</tex> разбиениях.<br />
<br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18088Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T23:39:59Z<p>178.178.21.14: /* Время работы алгоритма */</p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Оба этих класса помещаются в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.<br />
# Пока очередь не пуста, выполняем п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.<br />
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>. <br />
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for each <tex>R</tex> in <tex>P</tex> split by <tex>S</tex> <br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Время работы алгоритма=<br />
Время работы алгоритма равно O(|\Sigma| * n\log{n})</tex>, где <tex> n </tex> {---} количество состояний ДКА, а <tex> \Sigma </tex> {---} алфавит. Это следует из того, что каждое из ребер, а их порядка <tex> |\Sigma| * n </tex>, участвует не более чем в <tex> log{n}</tex> разбиениях.<br />
<br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''J. E. Hopcroft.'' An n log n algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18087Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T21:31:21Z<p>178.178.21.14: </p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Оба этих класса помещаются в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>a</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.<br />
# Пока очередь не пуста, выполняем п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.<br />
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>. <br />
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or <br />
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for each <tex>R</tex> in <tex>P</tex> split by <tex>S</tex> <br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Время работы алгоритма=<br />
Благодаря системе добавления классов состояний в очередь, каждое ребро будет рассмотрено не более чем <tex>\log{n}</tex> раз. А так как ребер у нас порядка <tex> |\Sigma| * n </tex> то получаем <tex> O(n\log{n})</tex><br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''J. E. Hopcroft.'' An n log n algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18086Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T19:49:27Z<p>178.178.21.14: </p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
{{Лемма<br />
|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>.<br />
|proof = <br />
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2</tex> <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_2</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/><br />
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/><br />
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> <br />
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется<br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> <br />
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> <br />
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.<br />
}}<br />
<br />
Алгорит Хопкрофта является улучшением простого алгоритма. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Меньший из них помещается в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>c</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>c</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также меньший из двух подклассов добавляется в очередь.<br />
# Пока очередь не пуста, алгоритм выполняет п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Корректность алгоритма=<br />
<br />
<br />
=Время работы алгоритма=<br />
Благодаря системе добавления классов состояний в очередь, каждое ребро будет рассмотрено не более чем <tex>\log{n}</tex> раз. А так как ребер у нас порядка <tex> |\Sigma| * n </tex> то получаем <tex> O(n\log{n})</tex><br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''J. E. Hopcroft.'' An n log n algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14http://neerc.ifmo.ru/wiki/index.php?title=%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%94%D0%9A%D0%90,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A5%D0%BE%D0%BF%D0%BA%D1%80%D0%BE%D1%84%D1%82%D0%B0_(%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_O(n_log_n))&diff=18085Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))2012-02-14T17:44:06Z<p>178.178.21.14: </p>
<hr />
<div>= Постановка задачи =<br />
Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.<br />
<br />
= Минимизация ДКА =<br />
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. <br />
<br />
= Простой алгоритм =<br />
{{Определение<br />
|definition =<br />
Класс <tex>S</tex> '''разбивает''' класс <tex>R</tex> по символу <tex>a</tex> на <tex>R_1</tex> и <tex>R_2</tex>, если <br />
# <tex>\forall r \in R_1 \,\,\, \delta(r, a) \in S</tex> <br />
# <tex>\forall r \in R_2 \,\,\, \delta(r, a) \notin S</tex> <br />
}} <br />
Если класс <tex>R</tex> может быть разбит по символу <tex>a</tex>, то он содержит хотя бы одну пару неэквивалентных состояний (их можно различить любой строкой начинающейся с символа <tex>a</tex>). Если класс нельзя разбить, то он состоит из эквивалентных состояний.<br />
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. <br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
<tex>W \leftarrow \{ F, Q \setminus F \}</tex><br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс не может быть разбит.<br />
<br />
= Алгоритм Хопкрофта=<br />
<br />
<br />
<br />
Алгорит Хопкрофта является улучшением простого алгоритма. <br />
<br />
Итеративно строим разбиение множества состояний следующим образом.<br />
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний и класс недопускающих состояний.<br />
# Меньший из них помещается в очередь.<br />
# Из очереди извлекается класс, далее именуемый как сплиттер.<br />
# Перебираются все символы из алфавита <tex>\Sigma</tex>, где <tex>c</tex> {{---}} текущий символ.<br />
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>c</tex> переходят в сплиттер, а второй из всех оставшихся. <br />
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также меньший из двух подклассов добавляется в очередь.<br />
# Пока очередь не пуста, алгоритм выполняет п.3 – п.6.<br />
<br />
===Псевдокод===<br />
<tex>Q</tex> {{---}} множество состояний ДКА.<br />
<tex>F</tex> {{---}} множество терминальных состояний.<br />
<tex>W</tex> {{---}} очередь.<br />
<tex>P</tex> {{---}} разбиение множества состояний ДКА.<br />
<tex>R</tex> {{---}} класс состояний ДКА.<br />
if <tex> |F| \le |Q \setminus F|</tex><br />
<tex>W</tex>.push(<tex>F</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>Q \setminus F</tex>)<br />
<tex>P \leftarrow \{ F, Q \setminus F \}</tex><br />
while not <tex>W</tex>.isEmpty() <br />
<tex>W</tex>.pop(<tex>S</tex>)<br />
for all <tex>a \in \Sigma</tex><br />
for all <tex>R</tex> in <tex>P</tex> <br />
<tex>R_1 = R \cap \delta^{-1} (S, a) </tex><br />
<tex>R_2 = R \setminus R_1</tex><br />
if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex><br />
replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
if <tex>R</tex> in <tex>W</tex><br />
replace <tex>R</tex> in <tex>W</tex> with <tex>R_1</tex> and <tex>R_2</tex><br />
else<br />
if <tex> |R_1| \le |R_2|</tex><br />
<tex>W</tex>.push(<tex>R_1</tex>)<br />
else<br />
<tex>W</tex>.push(<tex>R_2</tex>)<br />
=Корректность алгоритма=<br />
<br />
<br />
=Время работы алгоритма=<br />
Благодаря системе добавления классов состояний в очередь, каждое ребро будет рассмотрено не более чем <tex>\log{n}</tex> раз. А так как ребер у нас порядка <tex> |\Sigma| * n </tex> то получаем <tex> O(n\log{n})</tex><br />
= Литература =<br />
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)<br />
* ''J. E. Hopcroft.'' An n log n algorithm for minimizing states in a finite automaton. Technical Report CS-71-190, Stanford University, January 1971.<br />
<br />
[[Категория: Теория формальных языков]]<br />
[[Категория: Автоматы и регулярные языки]]</div>178.178.21.14