<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=94.25.228.16&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=94.25.228.16&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/94.25.228.16"/>
		<updated>2026-06-11T18:41:13Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59752</id>
		<title>Замкнутость разрешимых и перечислимых языков относительно теоретико-множественных и алгебраических операций</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59752"/>
				<updated>2017-01-16T17:42:42Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Разрешимые_(рекурсивные)_языки|разрешимы]], тогда следующие языки разрешимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} разрешающие программы для языков  &lt;br /&gt;
&amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать разрешающую программу (разрешитель) для каждого случая. &lt;br /&gt;
&lt;br /&gt;
* Разрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \lor (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; \overline{L_1}&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 0)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \backslash L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 0)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(y) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt;: &lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_1(x_2) == 1) \land \ldots \ \land (p_1(x_n) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения данного ему слова на подстроки и для каждой проверять принадлежность &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;. Если хотя бы в одном разбиении все подстроки будут принадлежать &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, то всё слово принадлежит &amp;lt;tex&amp;gt; L_1^* &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит. &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 : &amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_2(x_2) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения на два слова и проверять принадлежность первого слова &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и второго слова &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt;. Если хотя бы для одного разбиения оба разрешителя вернут 1, то слово принадлежит &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Перечислимые_языки|перечислимы]], тогда следующие языки перечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} полуразрешающие программы для языков &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать полуразрешающую программу для каждого случая. Заметим, что &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; могут зависнуть при использовании в полуразрешающей программе для соответствующего языка, но это допустимо.&lt;br /&gt;
&lt;br /&gt;
* Полуразрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''if''' &amp;lt;tex&amp;gt; (p_1(x)|_k == 1) \lor (p_2(x)|_k == 1) &amp;lt;/tex&amp;gt; &lt;br /&gt;
     &lt;br /&gt;
              '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(x) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(y) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1^*&amp;lt;/tex&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_1|_k(x_2) == 1) \land \ ... \ \land (p_1|_k(x_n) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_2|_k(x_2) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} перечислимы, тогда следующие языки могут быть неперечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Рассмотрим язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Предположим, что он перечислим. Тогда, имея какое-либо слово, мы можем одновременно запустить перечислители для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. В какой-то момент времени слово появится либо в выводе перечислителя для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, либо в выводе перечислителя для &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Тогда получится, что &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; разрешим, так как про любое слово можно сказать, принадлежит ли оно &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; или нет. Но мы знаем, что [[Разрешимые (рекурсивные) языки#Пример неразрешимого множества|существуют перечислимые, но неразрешимые языки]], следовательно, язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt; может быть неперечислим. &lt;br /&gt;
&lt;br /&gt;
Теперь рассмотрим &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt;. В качестве &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; возьмём язык, состоящий из всех слов. Тогда получится, что &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; {{---}} это &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt;. Про &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt; мы знаем, что он перечислим не всегда, поэтому и &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; не всегда перечислим.&lt;br /&gt;
}}&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Разрешимые (рекурсивные) языки]]&lt;br /&gt;
* [[Перечислимые языки]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* Н. К. Верещагин, А. Шень. Лекции по математической логике и теории алгоритмов. Часть 3. Вычислимые функции. — М.: МЦНМО, 1999. С. 134. ISBN 5-900916-36-7&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Теория вычислимости]]&lt;br /&gt;
[[Категория: Разрешимые и перечислимые языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59750</id>
		<title>Замкнутость разрешимых и перечислимых языков относительно теоретико-множественных и алгебраических операций</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59750"/>
				<updated>2017-01-16T17:39:07Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Разрешимые_(рекурсивные)_языки|разрешимы]], тогда следующие языки разрешимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} разрешающие программы для языков  &lt;br /&gt;
&amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать разрешающую программу (разрешитель) для каждого случая. &lt;br /&gt;
&lt;br /&gt;
* Разрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \lor (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; \overline{L_1}&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 0)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \backslash L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 0)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(y) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt;: &lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_1(x_2) == 1) \land \ldots \ \land (p_1(x_n) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения данного ему слова на подстроки и для каждой проверять принадлежность &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;. Если хотя бы в одном разбиении все подстроки будут принадлежать &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, то всё слово принадлежит &amp;lt;tex&amp;gt; L_1^* &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит. &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 : &amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_2(x_2) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения на два слова и проверять принадлежность первого слова &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и второго слова &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt;. Если хотя бы для одного разбиения оба разрешителя вернут 1, то слово принадлежит &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Перечислимые_языки|перечислимы]], тогда следующие языки перечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} полуразрешающие программы для языков &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать полуразрешающую программу для каждого случая. Заметим, что &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; могут зависнуть при использовании в полуразрешающей программе для соответствующего языка, но это допустимо.&lt;br /&gt;
&lt;br /&gt;
* Полуразрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''if''' &amp;lt;tex&amp;gt; (p_1(x)|_k == 1) \lor (p_2(x)|_k == 1) &amp;lt;/tex&amp;gt; &lt;br /&gt;
     &lt;br /&gt;
              '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(x) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(y) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1^*&amp;lt;/tex&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_1|_k(x_2) == 1) \land \ ... \ \land (p_1|_k(x_n) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_2|_k(x_2) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return''' &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; &lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} перечислимы, тогда следующие языки могут быть неперечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Рассмотрим язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Предположим, что он перечислим. Тогда, имея какое-либо слово, мы можем одновременно запустить перечислители для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. В какой-то момент времени слово появится либо в выводе перечислителя для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, либо в выводе перечислителя для &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Тогда получится, что &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; разрешим, так как про любое слово можно сказать, принадлежит ли оно &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; или нет. Но мы знаем, что [[Разрешимые (рекурсивные) языки#Пример неразрешимого множества|существуют перечислимые, но неразрешимые языки]], следовательно, язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt; может быть неперечислим. &lt;br /&gt;
&lt;br /&gt;
Теперь рассмотрим &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt;. В качестве &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; возьмём язык, состоящий из всех слов. Тогда получится, что &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; {{---}} это &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt;. Про &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt; мы знаем, что он перечислим не всегда, поэтому и &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; не всегда перечислим.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* Н. К. Верещагин, А. Шень. Лекции по математической логике и теории алгоритмов. Часть 3. Вычислимые функции. — М.: МЦНМО, 1999. С. 134. ISBN 5-900916-36-7&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Теория вычислимости]]&lt;br /&gt;
[[Категория: Разрешимые и перечислимые языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59746</id>
		<title>Замкнутость разрешимых и перечислимых языков относительно теоретико-множественных и алгебраических операций</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D0%BE%D1%81%D1%82%D1%8C_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B8%D0%BC%D1%8B%D1%85_%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D0%BC%D1%8B%D1%85_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2_%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D0%BA%D0%BE-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%B5%D0%B1%D1%80%D0%B0%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9&amp;diff=59746"/>
				<updated>2017-01-16T16:59:26Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Разрешимые_(рекурсивные)_языки|разрешимы]], тогда следующие языки разрешимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} разрешающие программы для языков  &lt;br /&gt;
&amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать разрешающую программу (разрешитель) для каждого случая. &lt;br /&gt;
&lt;br /&gt;
* Разрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \lor (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; \overline{L_1}&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 0)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \backslash L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(x) == 0)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;(p_1(x) == 1) \land (p_2(y) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt;: &lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_1(x_2) == 1) \land \ ... \ \land (p_1(x_n) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' 1&lt;br /&gt;
     '''return''' 0  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения данного ему слова на подстроки и для каждой проверять принадлежность &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;. Если хотя бы в одном разбиении все подстроки будут принадлежать &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, то всё слово принадлежит &amp;lt;tex&amp;gt; L_1^* &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит. &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 : &amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;(p_1(x_1) == 1) \land (p_2(x_2) == 1)&amp;lt;/tex&amp;gt; &lt;br /&gt;
             '''return''' 1&lt;br /&gt;
     '''return''' 0  &lt;br /&gt;
&lt;br /&gt;
Разрешитель будет перебирать все возможные разбиения на два слова и проверять принадлежность первого слова &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и второго слова &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt;. Если хотя бы для одного разбиения оба разрешителя вернут 1, то слово принадлежит &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;, иначе {{---}} не принадлежит.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} [[Перечислимые_языки|перечислимы]], тогда следующие языки перечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cup L_2&amp;lt;/tex&amp;gt; {{---}} объединение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \cap L_2&amp;lt;/tex&amp;gt; {{---}} пересечение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \times L_2&amp;lt;/tex&amp;gt; {{---}} декартово произведение &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1^*&amp;lt;/tex&amp;gt; {{---}} замыкание Клини &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 L_2&amp;lt;/tex&amp;gt; {{---}} конкатенация &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; {{---}} полуразрешающие программы для языков &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt; соответственно. Для доказательства достаточно написать полуразрешающую программу для каждого случая. Заметим, что &amp;lt;tex&amp;gt;p_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p_2&amp;lt;/tex&amp;gt; могут зависнуть при использовании в полуразрешающей программе для соответствующего языка, но это допустимо.&lt;br /&gt;
&lt;br /&gt;
* Полуразрешающая программа для языка &amp;lt;tex&amp;gt; L_1 \cup L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''if''' &amp;lt;tex&amp;gt; (p_1(x)|_k == 1) \lor (p_2(x)|_k == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
              '''return 1''' &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \cap L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(x) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return 1''' &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 \times L_2&amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(\langle x, y \rangle):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''if''' &amp;lt;tex&amp;gt; (p_1(x) == 1) \land (p_2(y) == 1) &amp;lt;/tex&amp;gt;      &lt;br /&gt;
         '''return 1''' &lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1^*&amp;lt;/tex&amp;gt;:&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^n \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_1|_k(x_2) == 1) \land \ ... \ \land (p_1|_k(x_n) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return 1'''&lt;br /&gt;
&lt;br /&gt;
* Для языка &amp;lt;tex&amp;gt; L_1 L_2 &amp;lt;/tex&amp;gt;: &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;tex&amp;gt;p(x):&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;k = 1 \ .. \ \infty&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''forall''' &amp;lt;tex&amp;gt;{\{x_i\}}_{i=1}^2 \in P &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} множество всевозможных разбиений слова &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на две подстроки&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;(p_1|_k(x_1) == 1) \land (p_2|_k(x_2) == 1)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''return 1'''&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
 Языки &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; L_2 &amp;lt;/tex&amp;gt; {{---}} перечислимы, тогда следующие языки могут быть неперечислимы:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tex&amp;gt;\overline{L_1}&amp;lt;/tex&amp;gt; {{---}} дополнение &amp;lt;tex&amp;gt;L_1\&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;L_1 \backslash L_2&amp;lt;/tex&amp;gt; {{---}} разность &amp;lt;tex&amp;gt;L_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;L_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Рассмотрим язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Предположим, что он перечислим. Тогда, имея какое-либо слово, мы можем одновременно запустить перечислители для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. В какой-то момент времени слово появится либо в выводе перечислителя для &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt;, либо в выводе перечислителя для &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt;. Тогда получится, что &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; разрешим, так как про любое слово можно сказать, принадлежит ли оно &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; или нет. Но мы знаем, что [[Разрешимые (рекурсивные) языки#Пример неразрешимого множества|существуют перечислимые, но неразрешимые языки]], следовательно, язык &amp;lt;tex&amp;gt; \overline{L_1} &amp;lt;/tex&amp;gt; может быть неперечислим. &lt;br /&gt;
&lt;br /&gt;
Теперь рассмотрим &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt;. В качестве &amp;lt;tex&amp;gt; L_1 &amp;lt;/tex&amp;gt; возьмём язык, состоящий из всех слов. Тогда получится, что &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; {{---}} это &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt;. Про &amp;lt;tex&amp;gt; \overline{L_2} &amp;lt;/tex&amp;gt; мы знаем, что он перечислим не всегда, поэтому и &amp;lt;tex&amp;gt; L_1 \backslash L_2 &amp;lt;/tex&amp;gt; не всегда перечислим.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* Н. К. Верещагин, А. Шень. Лекции по математической логике и теории алгоритмов. Часть 3. Вычислимые функции. — М.: МЦНМО, 1999. С. 134. ISBN 5-900916-36-7&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Теория вычислимости]]&lt;br /&gt;
[[Категория: Разрешимые и перечислимые языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://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))&amp;diff=59745</id>
		<title>Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))</title>
		<link rel="alternate" type="text/html" href="http://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))&amp;diff=59745"/>
				<updated>2017-01-16T16:54:14Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: /* Альтернативная реализация */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.&lt;br /&gt;
&lt;br /&gt;
== Минимизация ДКА ==&lt;br /&gt;
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. &lt;br /&gt;
&lt;br /&gt;
== Простой алгоритм ==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition =&lt;br /&gt;
Класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; '''разбивает''' класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_1 \,\,\, \delta(r, a) \in C&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin C&amp;lt;/tex&amp;gt; &lt;br /&gt;
}} &lt;br /&gt;
Если класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть разбит по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.&lt;br /&gt;
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. &lt;br /&gt;
&lt;br /&gt;
Итеративно строим разбиение множества состояний следующим образом.&lt;br /&gt;
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний &amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; и класс недопускающих состояний (&amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;).&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle Q \setminus F, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Каждый класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер (&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;), а второй из всех оставшихся (&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;). &lt;br /&gt;
# Если &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; разбился на два непустых подкласса (т.е. &amp;lt;tex&amp;gt; R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset &amp;lt;/tex&amp;gt;).&lt;br /&gt;
## В разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; заменяется на свои подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
## Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector'''&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
          replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_1,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_2,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Когда очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n^2)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; {{---}} количество состояний ДКА, а &amp;lt;tex&amp;gt; \Sigma &amp;lt;/tex&amp;gt; {{---}} алфавит. Это следует из того, что если пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса &amp;lt;tex&amp;gt;C_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;C_2&amp;lt;/tex&amp;gt;, причем можно гарантировать лишь следующее уменьшение размера: &amp;lt;tex&amp;gt;|C| \geqslant |C_i| + 1&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Хопкрофта==&lt;br /&gt;
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за &amp;lt;tex&amp;gt; O(n^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement = Класс &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt;, тогда разбиение всех классов (текущее разбиение) по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; любыми двумя классами из &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; эквивалентно разбиению всех классов с помощью &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_1&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_2  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt; \forall r \in B \,\,\, \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение. &amp;lt;br/&amp;gt;&lt;br /&gt;
Аналогично доказывается и для разбиения с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. &amp;lt;br/&amp;gt;&lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.&lt;br /&gt;
После замены класса &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; в разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; на его подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, как и раньше перебираем символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; уже есть в очереди, то согласно лемме можно просто заменить её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет в очереди, то достаточно добавить любую из пар &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;. Это следует из следующих соображений: &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть в разбиении только если в очередь были положены пары &amp;lt;tex&amp;gt;\langle R,\ a \rangle&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;\forall a \in \Sigma&amp;lt;/tex&amp;gt;, а поскольку в очереди пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; уже были разбиты по &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt; &lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// смотрим, есть ли пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; в очереди &amp;lt;/font&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// заменяем её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; если пара есть &amp;lt;/font&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// вставляем любую иначе&amp;lt;/font&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; (его нужно будет эффективно находить для каждой пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// находим классы, из состояний которых есть ребро в состояния сплиттера &amp;lt;/font&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// перебираем только классы входящие в &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Каждая итерация цикла &amp;lt;tex&amp;gt; \mathrm{while} &amp;lt;/tex&amp;gt; может быть выполнена за &amp;lt;tex&amp;gt; O(|Q| + |\mathtt{Inverse}|) &amp;lt;/tex&amp;gt; для текущей пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;. Покажем, как можно достичь этой оценки.&lt;br /&gt;
&lt;br /&gt;
Классы разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} номер класса, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; {{---}} номер класса (сплиттера),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
&lt;br /&gt;
Для обработки &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; за &amp;lt;tex&amp;gt;O(|Q| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; нам понадобится следующая структура:&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} список из номеров классов, содержащихся во множестве &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt; {{---}} целочисленный массив, где &amp;lt;tex&amp;gt;\mathtt{Count}[i]&amp;lt;/tex&amp;gt; хранит количество состояний из класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, которые содержатся в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; {{---}} массив, хранящий в &amp;lt;tex&amp;gt;\mathtt{Twin}[i]&amp;lt;/tex&amp;gt; номер нового класса, образовавшегося при разбиении класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} \leftarrow \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] == 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
           insert &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i]++&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             insert &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; //запишем в &amp;lt;tex&amp;gt;\mathtt{Twin[i]}&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;j \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            remove &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            add &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; j \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;// парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// swap за &amp;lt;tex&amp;gt;\mathtt{O(1)}&amp;lt;/tex&amp;gt; - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; // обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
             push &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; пара &amp;lt;tex&amp;gt;\langle i, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Кроме того, вместо очереди можно использовать вообще произвольную структуру, хранящую элементы, в том числе стэк, множество, т.к. порядок извлечения нам по сути не важен.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 1&lt;br /&gt;
|id = Лемма1&lt;br /&gt;
|statement =&lt;br /&gt;
Количество классов, созданных во время выполнения алгоритма, не превышает &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Представим дерево, которое соответствует операциям разделения классов на подклассы. Корнем этого дерева является все множество состояний &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt;. Листьями являются классы эквивалентности, оставшиеся после работы алгоритма. Так как дерево бинарное {{---}} каждый класс может породить лишь два новых, а количество листьев не может быть больше &amp;lt;tex&amp;gt;|Q|&amp;lt;/tex&amp;gt;, то количество узлов этого дерева не может быть больше &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;, что доказывает утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 2&lt;br /&gt;
|id = Лемма2&lt;br /&gt;
|statement = &lt;br /&gt;
Количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превышает &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Для доказательства этого утверждения достаточно показать, что количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; добавленных в очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;, так как на каждой итерации мы извлекаем одну пару из очереди.&lt;br /&gt;
&lt;br /&gt;
По [[#Лемма1 | лемме(1)]] количество классов не превосходит &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; элемент текущего разбиения. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\ a \in \Sigma&amp;lt;/tex&amp;gt; не может быть больше &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt;. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 3&lt;br /&gt;
|id = Лемма3&lt;br /&gt;
|statement = &lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p \in Q&amp;lt;/tex&amp;gt;. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которые мы удалим из очереди, не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt; для фиксированных &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Рассмотрим пару &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которую мы удаляем из очереди. И пусть &amp;lt;tex&amp;gt;\langle C',a \rangle&amp;lt;/tex&amp;gt; следующая пара, где &amp;lt;tex&amp;gt;p \in C'&amp;lt;/tex&amp;gt; и которую мы удалим из очереди. Согласно нашему алгоритму класс &amp;lt;tex&amp;gt;C'&amp;lt;/tex&amp;gt; мог появиться в очереди только после операции &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt;. Но после первого же разбиения класса &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; на подклассы мы добавим в очередь пару &amp;lt;tex&amp;gt;\langle C'', a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C''&amp;lt;/tex&amp;gt; меньший из образовавшихся подклассов, то есть &amp;lt;tex&amp;gt;|C''| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Так же заметим, что &amp;lt;tex&amp;gt;C' \subseteq C''&amp;lt;/tex&amp;gt;, а следовательно &amp;lt;tex&amp;gt;|C'| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Но тогда таких пар не может быть больше, чем &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 4&lt;br /&gt;
|id = Лемма4&lt;br /&gt;
|statement = &lt;br /&gt;
&amp;lt;tex&amp;gt;\sum |\mathtt{Inverse}|&amp;lt;/tex&amp;gt; по всем итерациям цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;x, y \in Q&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;. Зафиксируем эту тройку. Заметим, что количество раз, которое &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; встречается в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; при условии, что &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;, совпадает с числом удаленных из очереди пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;y \in C&amp;lt;/tex&amp;gt;. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. Просуммировав по всем &amp;lt;tex&amp;gt; x \in Q &amp;lt;/tex&amp;gt; и по всем &amp;lt;tex&amp;gt; a \in \Sigma&amp;lt;/tex&amp;gt; мы получим утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement =&lt;br /&gt;
Время работы алгоритма Хопкрофта равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Оценим, сколько времени занимает каждая часть алгоритма:&lt;br /&gt;
&lt;br /&gt;
*Построение массива &amp;lt;tex&amp;gt;\mathtt{Inv}&amp;lt;/tex&amp;gt; занимает &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt; времени. &lt;br /&gt;
&lt;br /&gt;
*По [[#Лемма2 | второй лемме]] количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*Операции с множеством &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; и разбиение классов на подклассы требуют &amp;lt;tex&amp;gt;O(\sum(|\mathtt{Inverse}|))&amp;lt;/tex&amp;gt; времени. Но по [[#Лемма4 | лемме(4)]] &amp;lt;tex&amp;gt;\sum(|\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;, то есть данная часть алгоритма выполняется за &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt; классов, из чего следует, что количество операций &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt; равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Итого, получается, что время работы алгоритма Хопкрофта не превышает &amp;lt;tex&amp;gt; O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Альтернативная реализация ===&lt;br /&gt;
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).&lt;br /&gt;
&lt;br /&gt;
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} индекс класса в &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь из пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       insert &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i] == \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Involved}[i] = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         add &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Перебираем ключи &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;j = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               remove &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               add &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за &amp;lt;tex&amp;gt;\mathtt{O(1)}&amp;lt;/tex&amp;gt; - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
               &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
               push &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Алгоритм Бржозовского]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)&lt;br /&gt;
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.&lt;br /&gt;
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.&lt;br /&gt;
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://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))&amp;diff=59744</id>
		<title>Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))</title>
		<link rel="alternate" type="text/html" href="http://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))&amp;diff=59744"/>
				<updated>2017-01-16T16:47:06Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: /* Псевдокод */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.&lt;br /&gt;
&lt;br /&gt;
== Минимизация ДКА ==&lt;br /&gt;
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. &lt;br /&gt;
&lt;br /&gt;
== Простой алгоритм ==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition =&lt;br /&gt;
Класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; '''разбивает''' класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_1 \,\,\, \delta(r, a) \in C&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin C&amp;lt;/tex&amp;gt; &lt;br /&gt;
}} &lt;br /&gt;
Если класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть разбит по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.&lt;br /&gt;
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. &lt;br /&gt;
&lt;br /&gt;
Итеративно строим разбиение множества состояний следующим образом.&lt;br /&gt;
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний &amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; и класс недопускающих состояний (&amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;).&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle Q \setminus F, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Каждый класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер (&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;), а второй из всех оставшихся (&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;). &lt;br /&gt;
# Если &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; разбился на два непустых подкласса (т.е. &amp;lt;tex&amp;gt; R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset &amp;lt;/tex&amp;gt;).&lt;br /&gt;
## В разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; заменяется на свои подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
## Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector'''&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
          replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_1,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_2,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Когда очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n^2)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; {{---}} количество состояний ДКА, а &amp;lt;tex&amp;gt; \Sigma &amp;lt;/tex&amp;gt; {{---}} алфавит. Это следует из того, что если пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса &amp;lt;tex&amp;gt;C_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;C_2&amp;lt;/tex&amp;gt;, причем можно гарантировать лишь следующее уменьшение размера: &amp;lt;tex&amp;gt;|C| \geqslant |C_i| + 1&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Хопкрофта==&lt;br /&gt;
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за &amp;lt;tex&amp;gt; O(n^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement = Класс &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt;, тогда разбиение всех классов (текущее разбиение) по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; любыми двумя классами из &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; эквивалентно разбиению всех классов с помощью &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_1&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_2  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt; \forall r \in B \,\,\, \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение. &amp;lt;br/&amp;gt;&lt;br /&gt;
Аналогично доказывается и для разбиения с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. &amp;lt;br/&amp;gt;&lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.&lt;br /&gt;
После замены класса &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; в разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; на его подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, как и раньше перебираем символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; уже есть в очереди, то согласно лемме можно просто заменить её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет в очереди, то достаточно добавить любую из пар &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;. Это следует из следующих соображений: &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть в разбиении только если в очередь были положены пары &amp;lt;tex&amp;gt;\langle R,\ a \rangle&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;\forall a \in \Sigma&amp;lt;/tex&amp;gt;, а поскольку в очереди пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; уже были разбиты по &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt; &lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// смотрим, есть ли пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; в очереди &amp;lt;/font&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// заменяем её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; если пара есть &amp;lt;/font&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// вставляем любую иначе&amp;lt;/font&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; (его нужно будет эффективно находить для каждой пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// находим классы, из состояний которых есть ребро в состояния сплиттера &amp;lt;/font&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// перебираем только классы входящие в &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Каждая итерация цикла &amp;lt;tex&amp;gt; \mathrm{while} &amp;lt;/tex&amp;gt; может быть выполнена за &amp;lt;tex&amp;gt; O(|Q| + |\mathtt{Inverse}|) &amp;lt;/tex&amp;gt; для текущей пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;. Покажем, как можно достичь этой оценки.&lt;br /&gt;
&lt;br /&gt;
Классы разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} номер класса, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; {{---}} номер класса (сплиттера),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
&lt;br /&gt;
Для обработки &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; за &amp;lt;tex&amp;gt;O(|Q| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; нам понадобится следующая структура:&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} список из номеров классов, содержащихся во множестве &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt; {{---}} целочисленный массив, где &amp;lt;tex&amp;gt;\mathtt{Count}[i]&amp;lt;/tex&amp;gt; хранит количество состояний из класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, которые содержатся в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; {{---}} массив, хранящий в &amp;lt;tex&amp;gt;\mathtt{Twin}[i]&amp;lt;/tex&amp;gt; номер нового класса, образовавшегося при разбиении класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} \leftarrow \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] == 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
           insert &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i]++&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             insert &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; //запишем в &amp;lt;tex&amp;gt;\mathtt{Twin[i]}&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;j \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            remove &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            add &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; j \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;// парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// swap за &amp;lt;tex&amp;gt;\mathtt{O(1)}&amp;lt;/tex&amp;gt; - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; // обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
             push &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; пара &amp;lt;tex&amp;gt;\langle i, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Кроме того, вместо очереди можно использовать вообще произвольную структуру, хранящую элементы, в том числе стэк, множество, т.к. порядок извлечения нам по сути не важен.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 1&lt;br /&gt;
|id = Лемма1&lt;br /&gt;
|statement =&lt;br /&gt;
Количество классов, созданных во время выполнения алгоритма, не превышает &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Представим дерево, которое соответствует операциям разделения классов на подклассы. Корнем этого дерева является все множество состояний &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt;. Листьями являются классы эквивалентности, оставшиеся после работы алгоритма. Так как дерево бинарное {{---}} каждый класс может породить лишь два новых, а количество листьев не может быть больше &amp;lt;tex&amp;gt;|Q|&amp;lt;/tex&amp;gt;, то количество узлов этого дерева не может быть больше &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;, что доказывает утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 2&lt;br /&gt;
|id = Лемма2&lt;br /&gt;
|statement = &lt;br /&gt;
Количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превышает &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Для доказательства этого утверждения достаточно показать, что количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; добавленных в очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;, так как на каждой итерации мы извлекаем одну пару из очереди.&lt;br /&gt;
&lt;br /&gt;
По [[#Лемма1 | лемме(1)]] количество классов не превосходит &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; элемент текущего разбиения. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\ a \in \Sigma&amp;lt;/tex&amp;gt; не может быть больше &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt;. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 3&lt;br /&gt;
|id = Лемма3&lt;br /&gt;
|statement = &lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p \in Q&amp;lt;/tex&amp;gt;. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которые мы удалим из очереди, не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt; для фиксированных &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Рассмотрим пару &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которую мы удаляем из очереди. И пусть &amp;lt;tex&amp;gt;\langle C',a \rangle&amp;lt;/tex&amp;gt; следующая пара, где &amp;lt;tex&amp;gt;p \in C'&amp;lt;/tex&amp;gt; и которую мы удалим из очереди. Согласно нашему алгоритму класс &amp;lt;tex&amp;gt;C'&amp;lt;/tex&amp;gt; мог появиться в очереди только после операции &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt;. Но после первого же разбиения класса &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; на подклассы мы добавим в очередь пару &amp;lt;tex&amp;gt;\langle C'', a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C''&amp;lt;/tex&amp;gt; меньший из образовавшихся подклассов, то есть &amp;lt;tex&amp;gt;|C''| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Так же заметим, что &amp;lt;tex&amp;gt;C' \subseteq C''&amp;lt;/tex&amp;gt;, а следовательно &amp;lt;tex&amp;gt;|C'| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Но тогда таких пар не может быть больше, чем &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 4&lt;br /&gt;
|id = Лемма4&lt;br /&gt;
|statement = &lt;br /&gt;
&amp;lt;tex&amp;gt;\sum |\mathtt{Inverse}|&amp;lt;/tex&amp;gt; по всем итерациям цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;x, y \in Q&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;. Зафиксируем эту тройку. Заметим, что количество раз, которое &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; встречается в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; при условии, что &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;, совпадает с числом удаленных из очереди пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;y \in C&amp;lt;/tex&amp;gt;. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. Просуммировав по всем &amp;lt;tex&amp;gt; x \in Q &amp;lt;/tex&amp;gt; и по всем &amp;lt;tex&amp;gt; a \in \Sigma&amp;lt;/tex&amp;gt; мы получим утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement =&lt;br /&gt;
Время работы алгоритма Хопкрофта равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Оценим, сколько времени занимает каждая часть алгоритма:&lt;br /&gt;
&lt;br /&gt;
*Построение массива &amp;lt;tex&amp;gt;\mathtt{Inv}&amp;lt;/tex&amp;gt; занимает &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt; времени. &lt;br /&gt;
&lt;br /&gt;
*По [[#Лемма2 | второй лемме]] количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*Операции с множеством &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; и разбиение классов на подклассы требуют &amp;lt;tex&amp;gt;O(\sum(|\mathtt{Inverse}|))&amp;lt;/tex&amp;gt; времени. Но по [[#Лемма4 | лемме(4)]] &amp;lt;tex&amp;gt;\sum(|\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;, то есть данная часть алгоритма выполняется за &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt; классов, из чего следует, что количество операций &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt; равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Итого, получается, что время работы алгоритма Хопкрофта не превышает &amp;lt;tex&amp;gt; O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Альтернативная реализация ===&lt;br /&gt;
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).&lt;br /&gt;
&lt;br /&gt;
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} индекс класса в &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь из пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''insert''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i] == \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Involved}[i] = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Перебираем ключи &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;j = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
               &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Алгоритм Бржозовского]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)&lt;br /&gt;
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.&lt;br /&gt;
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.&lt;br /&gt;
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://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))&amp;diff=59743</id>
		<title>Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))</title>
		<link rel="alternate" type="text/html" href="http://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))&amp;diff=59743"/>
				<updated>2017-01-16T16:46:23Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: /* Реализация */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.&lt;br /&gt;
&lt;br /&gt;
== Минимизация ДКА ==&lt;br /&gt;
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. &lt;br /&gt;
&lt;br /&gt;
== Простой алгоритм ==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition =&lt;br /&gt;
Класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; '''разбивает''' класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_1 \,\,\, \delta(r, a) \in C&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin C&amp;lt;/tex&amp;gt; &lt;br /&gt;
}} &lt;br /&gt;
Если класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть разбит по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.&lt;br /&gt;
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. &lt;br /&gt;
&lt;br /&gt;
Итеративно строим разбиение множества состояний следующим образом.&lt;br /&gt;
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний &amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; и класс недопускающих состояний (&amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;).&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle Q \setminus F, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Каждый класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер (&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;), а второй из всех оставшихся (&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;). &lt;br /&gt;
# Если &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; разбился на два непустых подкласса (т.е. &amp;lt;tex&amp;gt; R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset &amp;lt;/tex&amp;gt;).&lt;br /&gt;
## В разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; заменяется на свои подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
## Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''pair&amp;lt;set, set&amp;gt;''' '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
          replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_1,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_2,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Когда очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n^2)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; {{---}} количество состояний ДКА, а &amp;lt;tex&amp;gt; \Sigma &amp;lt;/tex&amp;gt; {{---}} алфавит. Это следует из того, что если пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса &amp;lt;tex&amp;gt;C_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;C_2&amp;lt;/tex&amp;gt;, причем можно гарантировать лишь следующее уменьшение размера: &amp;lt;tex&amp;gt;|C| \geqslant |C_i| + 1&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Хопкрофта==&lt;br /&gt;
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за &amp;lt;tex&amp;gt; O(n^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement = Класс &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt;, тогда разбиение всех классов (текущее разбиение) по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; любыми двумя классами из &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; эквивалентно разбиению всех классов с помощью &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_1&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_2  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt; \forall r \in B \,\,\, \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение. &amp;lt;br/&amp;gt;&lt;br /&gt;
Аналогично доказывается и для разбиения с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. &amp;lt;br/&amp;gt;&lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.&lt;br /&gt;
После замены класса &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; в разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; на его подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, как и раньше перебираем символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; уже есть в очереди, то согласно лемме можно просто заменить её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет в очереди, то достаточно добавить любую из пар &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;. Это следует из следующих соображений: &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть в разбиении только если в очередь были положены пары &amp;lt;tex&amp;gt;\langle R,\ a \rangle&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;\forall a \in \Sigma&amp;lt;/tex&amp;gt;, а поскольку в очереди пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; уже были разбиты по &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt; &lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// смотрим, есть ли пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; в очереди &amp;lt;/font&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// заменяем её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; если пара есть &amp;lt;/font&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// вставляем любую иначе&amp;lt;/font&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; (его нужно будет эффективно находить для каждой пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// находим классы, из состояний которых есть ребро в состояния сплиттера &amp;lt;/font&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// перебираем только классы входящие в &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Каждая итерация цикла &amp;lt;tex&amp;gt; \mathrm{while} &amp;lt;/tex&amp;gt; может быть выполнена за &amp;lt;tex&amp;gt; O(|Q| + |\mathtt{Inverse}|) &amp;lt;/tex&amp;gt; для текущей пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;. Покажем, как можно достичь этой оценки.&lt;br /&gt;
&lt;br /&gt;
Классы разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} номер класса, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; {{---}} номер класса (сплиттера),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
&lt;br /&gt;
Для обработки &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; за &amp;lt;tex&amp;gt;O(|Q| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; нам понадобится следующая структура:&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} список из номеров классов, содержащихся во множестве &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt; {{---}} целочисленный массив, где &amp;lt;tex&amp;gt;\mathtt{Count}[i]&amp;lt;/tex&amp;gt; хранит количество состояний из класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, которые содержатся в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; {{---}} массив, хранящий в &amp;lt;tex&amp;gt;\mathtt{Twin}[i]&amp;lt;/tex&amp;gt; номер нового класса, образовавшегося при разбиении класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''vector''' &lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} \leftarrow \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] == 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
           insert &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i]++&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             insert &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; //запишем в &amp;lt;tex&amp;gt;\mathtt{Twin[i]}&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;j \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            remove &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            add &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; j \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;// парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// swap за &amp;lt;tex&amp;gt;\mathtt{O(1)}&amp;lt;/tex&amp;gt; - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt; // обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
             push &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; пара &amp;lt;tex&amp;gt;\langle i, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Кроме того, вместо очереди можно использовать вообще произвольную структуру, хранящую элементы, в том числе стэк, множество, т.к. порядок извлечения нам по сути не важен.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 1&lt;br /&gt;
|id = Лемма1&lt;br /&gt;
|statement =&lt;br /&gt;
Количество классов, созданных во время выполнения алгоритма, не превышает &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Представим дерево, которое соответствует операциям разделения классов на подклассы. Корнем этого дерева является все множество состояний &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt;. Листьями являются классы эквивалентности, оставшиеся после работы алгоритма. Так как дерево бинарное {{---}} каждый класс может породить лишь два новых, а количество листьев не может быть больше &amp;lt;tex&amp;gt;|Q|&amp;lt;/tex&amp;gt;, то количество узлов этого дерева не может быть больше &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;, что доказывает утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 2&lt;br /&gt;
|id = Лемма2&lt;br /&gt;
|statement = &lt;br /&gt;
Количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превышает &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Для доказательства этого утверждения достаточно показать, что количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; добавленных в очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;, так как на каждой итерации мы извлекаем одну пару из очереди.&lt;br /&gt;
&lt;br /&gt;
По [[#Лемма1 | лемме(1)]] количество классов не превосходит &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; элемент текущего разбиения. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\ a \in \Sigma&amp;lt;/tex&amp;gt; не может быть больше &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt;. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 3&lt;br /&gt;
|id = Лемма3&lt;br /&gt;
|statement = &lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p \in Q&amp;lt;/tex&amp;gt;. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которые мы удалим из очереди, не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt; для фиксированных &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Рассмотрим пару &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которую мы удаляем из очереди. И пусть &amp;lt;tex&amp;gt;\langle C',a \rangle&amp;lt;/tex&amp;gt; следующая пара, где &amp;lt;tex&amp;gt;p \in C'&amp;lt;/tex&amp;gt; и которую мы удалим из очереди. Согласно нашему алгоритму класс &amp;lt;tex&amp;gt;C'&amp;lt;/tex&amp;gt; мог появиться в очереди только после операции &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt;. Но после первого же разбиения класса &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; на подклассы мы добавим в очередь пару &amp;lt;tex&amp;gt;\langle C'', a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C''&amp;lt;/tex&amp;gt; меньший из образовавшихся подклассов, то есть &amp;lt;tex&amp;gt;|C''| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Так же заметим, что &amp;lt;tex&amp;gt;C' \subseteq C''&amp;lt;/tex&amp;gt;, а следовательно &amp;lt;tex&amp;gt;|C'| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Но тогда таких пар не может быть больше, чем &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 4&lt;br /&gt;
|id = Лемма4&lt;br /&gt;
|statement = &lt;br /&gt;
&amp;lt;tex&amp;gt;\sum |\mathtt{Inverse}|&amp;lt;/tex&amp;gt; по всем итерациям цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;x, y \in Q&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;. Зафиксируем эту тройку. Заметим, что количество раз, которое &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; встречается в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; при условии, что &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;, совпадает с числом удаленных из очереди пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;y \in C&amp;lt;/tex&amp;gt;. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. Просуммировав по всем &amp;lt;tex&amp;gt; x \in Q &amp;lt;/tex&amp;gt; и по всем &amp;lt;tex&amp;gt; a \in \Sigma&amp;lt;/tex&amp;gt; мы получим утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement =&lt;br /&gt;
Время работы алгоритма Хопкрофта равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Оценим, сколько времени занимает каждая часть алгоритма:&lt;br /&gt;
&lt;br /&gt;
*Построение массива &amp;lt;tex&amp;gt;\mathtt{Inv}&amp;lt;/tex&amp;gt; занимает &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt; времени. &lt;br /&gt;
&lt;br /&gt;
*По [[#Лемма2 | второй лемме]] количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*Операции с множеством &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; и разбиение классов на подклассы требуют &amp;lt;tex&amp;gt;O(\sum(|\mathtt{Inverse}|))&amp;lt;/tex&amp;gt; времени. Но по [[#Лемма4 | лемме(4)]] &amp;lt;tex&amp;gt;\sum(|\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;, то есть данная часть алгоритма выполняется за &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt; классов, из чего следует, что количество операций &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt; равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Итого, получается, что время работы алгоритма Хопкрофта не превышает &amp;lt;tex&amp;gt; O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Альтернативная реализация ===&lt;br /&gt;
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).&lt;br /&gt;
&lt;br /&gt;
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} индекс класса в &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь из пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''insert''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i] == \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Involved}[i] = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Перебираем ключи &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;j = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
               &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Алгоритм Бржозовского]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)&lt;br /&gt;
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.&lt;br /&gt;
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.&lt;br /&gt;
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://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))&amp;diff=59741</id>
		<title>Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))</title>
		<link rel="alternate" type="text/html" href="http://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))&amp;diff=59741"/>
				<updated>2017-01-16T15:01:57Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: /* Реализация */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.&lt;br /&gt;
&lt;br /&gt;
== Минимизация ДКА ==&lt;br /&gt;
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. &lt;br /&gt;
&lt;br /&gt;
== Простой алгоритм ==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition =&lt;br /&gt;
Класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; '''разбивает''' класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_1 \,\,\, \delta(r, a) \in C&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin C&amp;lt;/tex&amp;gt; &lt;br /&gt;
}} &lt;br /&gt;
Если класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть разбит по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.&lt;br /&gt;
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. &lt;br /&gt;
&lt;br /&gt;
Итеративно строим разбиение множества состояний следующим образом.&lt;br /&gt;
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний &amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; и класс недопускающих состояний (&amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;).&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle Q \setminus F, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Каждый класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер (&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;), а второй из всех оставшихся (&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;). &lt;br /&gt;
# Если &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; разбился на два непустых подкласса (т.е. &amp;lt;tex&amp;gt; R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset &amp;lt;/tex&amp;gt;).&lt;br /&gt;
## В разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; заменяется на свои подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
## Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''pair&amp;lt;set, set&amp;gt;''' '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
          replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_1,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_2,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Когда очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n^2)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; {{---}} количество состояний ДКА, а &amp;lt;tex&amp;gt; \Sigma &amp;lt;/tex&amp;gt; {{---}} алфавит. Это следует из того, что если пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса &amp;lt;tex&amp;gt;C_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;C_2&amp;lt;/tex&amp;gt;, причем можно гарантировать лишь следующее уменьшение размера: &amp;lt;tex&amp;gt;|C| \geqslant |C_i| + 1&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Хопкрофта==&lt;br /&gt;
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за &amp;lt;tex&amp;gt; O(n^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement = Класс &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt;, тогда разбиение всех классов (текущее разбиение) по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; любыми двумя классами из &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; эквивалентно разбиению всех классов с помощью &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_1&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_2  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt; \forall r \in B \,\,\, \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение. &amp;lt;br/&amp;gt;&lt;br /&gt;
Аналогично доказывается и для разбиения с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. &amp;lt;br/&amp;gt;&lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.&lt;br /&gt;
После замены класса &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; в разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; на его подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, как и раньше перебираем символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; уже есть в очереди, то согласно лемме можно просто заменить её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет в очереди, то достаточно добавить любую из пар &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;. Это следует из следующих соображений: &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть в разбиении только если в очередь были положены пары &amp;lt;tex&amp;gt;\langle R,\ a \rangle&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;\forall a \in \Sigma&amp;lt;/tex&amp;gt;, а поскольку в очереди пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; уже были разбиты по &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;: '''pair&amp;lt;set, set&amp;gt;''' &lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt; &lt;br /&gt;
         replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// смотрим, есть ли пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; в очереди &amp;lt;/font&amp;gt;&lt;br /&gt;
           remove &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// заменяем её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; если пара есть &amp;lt;/font&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;// вставляем любую иначе&amp;lt;/font&amp;gt;&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              push &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; (его нужно будет эффективно находить для каждой пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''push''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop''' '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''replace''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; '''with''' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''remove''' &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Каждая итерация цикла &amp;lt;tex&amp;gt; \mathrm{while} &amp;lt;/tex&amp;gt; может быть выполнена за &amp;lt;tex&amp;gt; O(|Q| + |\mathtt{Inverse}|) &amp;lt;/tex&amp;gt; для текущей пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;. Покажем, как можно достичь этой оценки.&lt;br /&gt;
&lt;br /&gt;
Классы разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} номер класса, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; {{---}} номер класса (сплиттера),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
&lt;br /&gt;
Для обработки &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; за &amp;lt;tex&amp;gt;O(|Q| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; нам понадобится следующая структура:&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} список из номеров классов, содержащихся во множестве &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt; {{---}} целочисленный массив, где &amp;lt;tex&amp;gt;\mathtt{Count}[i]&amp;lt;/tex&amp;gt; хранит количество состояний из класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, которые содержатся в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; {{---}} массив, хранящий в &amp;lt;tex&amp;gt;\mathtt{Twin}[i]&amp;lt;/tex&amp;gt; номер нового класса, образовавшегося при разбиении класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''push''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop''' '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} \leftarrow \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] == 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''insert''' &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i]++&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;\mathtt{Twin[i]}&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;j \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; j \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; пара &amp;lt;tex&amp;gt;\langle i, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Кроме того, вместо очереди можно использовать вообще произвольную струтуру, хранящую элементы, в том числе стэк, множество, т.к. порядок извлечения нам по сути не важен.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 1&lt;br /&gt;
|id = Лемма1&lt;br /&gt;
|statement =&lt;br /&gt;
Количество классов, созданных во время выполнения алгоритма, не превышает &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Представим дерево, которое соответствует операциям разделения классов на подклассы. Корнем этого дерева является все множество состояний &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt;. Листьями являются классы эквивалентности, оставшиеся после работы алгоритма. Так как дерево бинарное {{---}} каждый класс может породить лишь два новых, а количество листьев не может быть больше &amp;lt;tex&amp;gt;|Q|&amp;lt;/tex&amp;gt;, то количество узлов этого дерева не может быть больше &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;, что доказывает утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 2&lt;br /&gt;
|id = Лемма2&lt;br /&gt;
|statement = &lt;br /&gt;
Количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превышает &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Для доказательства этого утверждения достаточно показать, что количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; добавленных в очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;, так как на каждой итерации мы извлекаем одну пару из очереди.&lt;br /&gt;
&lt;br /&gt;
По [[#Лемма1 | лемме(1)]] количество классов не превосходит &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; элемент текущего разбиения. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\ a \in \Sigma&amp;lt;/tex&amp;gt; не может быть больше &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt;. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 3&lt;br /&gt;
|id = Лемма3&lt;br /&gt;
|statement = &lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p \in Q&amp;lt;/tex&amp;gt;. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которые мы удалим из очереди, не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt; для фиксированных &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Рассмотрим пару &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которую мы удаляем из очереди. И пусть &amp;lt;tex&amp;gt;\langle C',a \rangle&amp;lt;/tex&amp;gt; следующая пара, где &amp;lt;tex&amp;gt;p \in C'&amp;lt;/tex&amp;gt; и которую мы удалим из очереди. Согласно нашему алгоритму класс &amp;lt;tex&amp;gt;C'&amp;lt;/tex&amp;gt; мог появиться в очереди только после операции &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt;. Но после первого же разбиения класса &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; на подклассы мы добавим в очередь пару &amp;lt;tex&amp;gt;\langle C'', a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C''&amp;lt;/tex&amp;gt; меньший из образовавшихся подклассов, то есть &amp;lt;tex&amp;gt;|C''| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Так же заметим, что &amp;lt;tex&amp;gt;C' \subseteq C''&amp;lt;/tex&amp;gt;, а следовательно &amp;lt;tex&amp;gt;|C'| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Но тогда таких пар не может быть больше, чем &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 4&lt;br /&gt;
|id = Лемма4&lt;br /&gt;
|statement = &lt;br /&gt;
&amp;lt;tex&amp;gt;\sum |\mathtt{Inverse}|&amp;lt;/tex&amp;gt; по всем итерациям цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;x, y \in Q&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;. Зафиксируем эту тройку. Заметим, что количество раз, которое &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; встречается в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; при условии, что &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;, совпадает с числом удаленных из очереди пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;y \in C&amp;lt;/tex&amp;gt;. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. Просуммировав по всем &amp;lt;tex&amp;gt; x \in Q &amp;lt;/tex&amp;gt; и по всем &amp;lt;tex&amp;gt; a \in \Sigma&amp;lt;/tex&amp;gt; мы получим утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement =&lt;br /&gt;
Время работы алгоритма Хопкрофта равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Оценим, сколько времени занимает каждая часть алгоритма:&lt;br /&gt;
&lt;br /&gt;
*Построение массива &amp;lt;tex&amp;gt;\mathtt{Inv}&amp;lt;/tex&amp;gt; занимает &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt; времени. &lt;br /&gt;
&lt;br /&gt;
*По [[#Лемма2 | второй лемме]] количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*Операции с множеством &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; и разбиение классов на подклассы требуют &amp;lt;tex&amp;gt;O(\sum(|\mathtt{Inverse}|))&amp;lt;/tex&amp;gt; времени. Но по [[#Лемма4 | лемме(4)]] &amp;lt;tex&amp;gt;\sum(|\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;, то есть данная часть алгоритма выполняется за &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt; классов, из чего следует, что количество операций &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt; равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Итого, получается, что время работы алгоритма Хопкрофта не превышает &amp;lt;tex&amp;gt; O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Альтернативная реализация ===&lt;br /&gt;
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).&lt;br /&gt;
&lt;br /&gt;
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} индекс класса в &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь из пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''insert''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i] == \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Involved}[i] = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Перебираем ключи &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;j = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
               &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Алгоритм Бржозовского]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)&lt;br /&gt;
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.&lt;br /&gt;
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.&lt;br /&gt;
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	<entry>
		<id>http://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))&amp;diff=59739</id>
		<title>Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))</title>
		<link rel="alternate" type="text/html" href="http://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))&amp;diff=59739"/>
				<updated>2017-01-16T14:42:48Z</updated>
		
		<summary type="html">&lt;p&gt;94.25.228.16: /* Псевдокод */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть дан автомат, распознающий определенный язык. Требуется найти [[ Эквивалентность_состояний_ДКА | эквивалентный автомат]] с наименьшим количеством состояний.&lt;br /&gt;
&lt;br /&gt;
== Минимизация ДКА ==&lt;br /&gt;
Если в ДКА существуют два [[ Эквивалентность_состояний_ДКА | эквивалентных состояния]], то при их объединении мы получим [[ Эквивалентность_состояний_ДКА | эквивалентный ДКА]], так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА. &lt;br /&gt;
&lt;br /&gt;
== Простой алгоритм ==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition =&lt;br /&gt;
Класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; '''разбивает''' класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_1 \,\,\, \delta(r, a) \in C&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin C&amp;lt;/tex&amp;gt; &lt;br /&gt;
}} &lt;br /&gt;
Если класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть разбит по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний.&lt;br /&gt;
Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно. &lt;br /&gt;
&lt;br /&gt;
Итеративно строим разбиение множества состояний следующим образом.&lt;br /&gt;
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний &amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; и класс недопускающих состояний (&amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;).&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle Q \setminus F, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Каждый класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер (&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;), а второй из всех оставшихся (&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;). &lt;br /&gt;
# Если &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; разбился на два непустых подкласса (т.е. &amp;lt;tex&amp;gt; R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset &amp;lt;/tex&amp;gt;).&lt;br /&gt;
## В разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; класс &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; заменяется на свои подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
## Перебираются символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  '''pair&amp;lt;set, set&amp;gt;''' '''function''' findEquivalenceClasses&amp;lt;tex&amp;gt;(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      push &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; pop '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
          replace &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; with &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
          '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_1,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            insert &amp;lt;tex&amp;gt;\langle R_2,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Когда очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n^2)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; {{---}} количество состояний ДКА, а &amp;lt;tex&amp;gt; \Sigma &amp;lt;/tex&amp;gt; {{---}} алфавит. Это следует из того, что если пара &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса &amp;lt;tex&amp;gt;C_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;C_2&amp;lt;/tex&amp;gt;, причем можно гарантировать лишь следующее уменьшение размера: &amp;lt;tex&amp;gt;|C| \geqslant |C_i| + 1&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| \cdot n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Хопкрофта==&lt;br /&gt;
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за &amp;lt;tex&amp;gt; O(n^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement = Класс &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt;, тогда разбиение всех классов (текущее разбиение) по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; любыми двумя классами из &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; эквивалентно разбиению всех классов с помощью &amp;lt;tex&amp;gt;R, R_1, R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_1&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_2  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt; \forall r \in B \,\,\, \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение. &amp;lt;br/&amp;gt;&lt;br /&gt;
Аналогично доказывается и для разбиения с помощью &amp;lt;tex&amp;gt;R &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. &amp;lt;br/&amp;gt;&lt;br /&gt;
Разобьем все классы с помощью &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; R_2&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, тогда для любого класса &amp;lt;tex&amp;gt;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt;  &lt;br /&gt;
А так как &amp;lt;tex&amp;gt;R = R_1 \cup R_2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_1 \cap R_2 = \varnothing&amp;lt;/tex&amp;gt; то выполняется&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R  \ \lor&amp;lt;/tex&amp;gt;&lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; &lt;br /&gt;
Из этого следует, что разбиение всех классов с помощью &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; никак не повлияет на текущее разбиение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.&lt;br /&gt;
После замены класса &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; в разбиении &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; на его подклассы &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;, как и раньше перебираем символы алфавита &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пара &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; уже есть в очереди, то согласно лемме можно просто заменить её на пары &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет в очереди, то достаточно добавить любую из пар &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt;. Это следует из следующих соображений: &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; может быть в разбиении только если в очередь были положены пары &amp;lt;tex&amp;gt;\langle R,\ a \rangle&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;\forall a \in \Sigma&amp;lt;/tex&amp;gt;, а поскольку в очереди пары &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; уже были разбиты по &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Q}&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{F}&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{\delta}&amp;lt;/tex&amp;gt; {{---}} функция перехода (&amp;lt;tex&amp;gt;\delta (r,\ a)&amp;lt;/tex&amp;gt; {{---}} состояние, в которое можно совершить переход из &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{R}&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''push''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop''' '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''replace''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; '''with''' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''remove''' &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &lt;br /&gt;
         &lt;br /&gt;
&lt;br /&gt;
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; (его нужно будет эффективно находить для каждой пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;\mathtt{S} \leftarrow \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''push''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''while''' &amp;lt;tex&amp;gt;\mathtt{S} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop''' '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''for''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt; R_1, R_2 \leftarrow &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\mathtt{split}(R,\ C,\ a)&amp;lt;/tex&amp;gt;&lt;br /&gt;
        '''if''' &amp;lt;tex&amp;gt; R_1 \ne \varnothing &amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt; R_2 \ne \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''replace''' &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; '''with''' &amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\langle R,\ c \rangle&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt; \mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''remove''' &amp;lt;tex&amp;gt;\langle R, c \rangle&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''else'''&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt; |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| &amp;lt;/tex&amp;gt;&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_1, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''else'''&lt;br /&gt;
              '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{S}&amp;lt;/tex&amp;gt;&lt;br /&gt;
    '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Каждая итерация цикла &amp;lt;tex&amp;gt; \mathrm{while} &amp;lt;/tex&amp;gt; может быть выполнена за &amp;lt;tex&amp;gt; O(|Q| + |\mathtt{Inverse}|) &amp;lt;/tex&amp;gt; для текущей пары &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;. Покажем, как можно достичь этой оценки.&lt;br /&gt;
&lt;br /&gt;
Классы разбиения &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} номер класса, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; {{---}} номер класса (сплиттера),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
&lt;br /&gt;
Для обработки &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; за &amp;lt;tex&amp;gt;O(|Q| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; нам понадобится следующая структура:&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} список из номеров классов, содержащихся во множестве &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt; {{---}} целочисленный массив, где &amp;lt;tex&amp;gt;\mathtt{Count}[i]&amp;lt;/tex&amp;gt; хранит количество состояний из класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, которые содержатся в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; {{---}} массив, хранящий в &amp;lt;tex&amp;gt;\mathtt{Twin}[i]&amp;lt;/tex&amp;gt; номер нового класса, образовавшегося при разбиении класса &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''push''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop''' '''from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} \leftarrow \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] == 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''insert''' &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i]++&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;\mathtt{Twin[i]}&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;j \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;j = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; j \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
           '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[i] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; пара &amp;lt;tex&amp;gt;\langle i, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же &amp;lt;tex&amp;gt;\langle \mathtt{Twin}[i], c \rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Кроме того, вместо очереди можно использовать вообще произвольную струтуру, хранящую элементы, в том числе стэк, множество, т.к. порядок извлечения нам по сути не важен.&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 1&lt;br /&gt;
|id = Лемма1&lt;br /&gt;
|statement =&lt;br /&gt;
Количество классов, созданных во время выполнения алгоритма, не превышает &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Представим дерево, которое соответствует операциям разделения классов на подклассы. Корнем этого дерева является все множество состояний &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt;. Листьями являются классы эквивалентности, оставшиеся после работы алгоритма. Так как дерево бинарное {{---}} каждый класс может породить лишь два новых, а количество листьев не может быть больше &amp;lt;tex&amp;gt;|Q|&amp;lt;/tex&amp;gt;, то количество узлов этого дерева не может быть больше &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;, что доказывает утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 2&lt;br /&gt;
|id = Лемма2&lt;br /&gt;
|statement = &lt;br /&gt;
Количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превышает &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Для доказательства этого утверждения достаточно показать, что количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; добавленных в очередь &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;, так как на каждой итерации мы извлекаем одну пару из очереди.&lt;br /&gt;
&lt;br /&gt;
По [[#Лемма1 | лемме(1)]] количество классов не превосходит &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; элемент текущего разбиения. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\ a \in \Sigma&amp;lt;/tex&amp;gt; не может быть больше &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt;. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит &amp;lt;tex&amp;gt; 2 |\Sigma| |Q| &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 3&lt;br /&gt;
|id = Лемма3&lt;br /&gt;
|statement = &lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p \in Q&amp;lt;/tex&amp;gt;. Тогда количество пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которые мы удалим из очереди, не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt; для фиксированных &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Рассмотрим пару &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p \in C&amp;lt;/tex&amp;gt;, которую мы удаляем из очереди. И пусть &amp;lt;tex&amp;gt;\langle C',a \rangle&amp;lt;/tex&amp;gt; следующая пара, где &amp;lt;tex&amp;gt;p \in C'&amp;lt;/tex&amp;gt; и которую мы удалим из очереди. Согласно нашему алгоритму класс &amp;lt;tex&amp;gt;C'&amp;lt;/tex&amp;gt; мог появиться в очереди только после операции &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt;. Но после первого же разбиения класса &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; на подклассы мы добавим в очередь пару &amp;lt;tex&amp;gt;\langle C'', a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;C''&amp;lt;/tex&amp;gt; меньший из образовавшихся подклассов, то есть &amp;lt;tex&amp;gt;|C''| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Так же заметим, что &amp;lt;tex&amp;gt;C' \subseteq C''&amp;lt;/tex&amp;gt;, а следовательно &amp;lt;tex&amp;gt;|C'| \leqslant |C| \ / \ 2&amp;lt;/tex&amp;gt;. Но тогда таких пар не может быть больше, чем &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|about = 4&lt;br /&gt;
|id = Лемма4&lt;br /&gt;
|statement = &lt;br /&gt;
&amp;lt;tex&amp;gt;\sum |\mathtt{Inverse}|&amp;lt;/tex&amp;gt; по всем итерациям цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;x, y \in Q&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;. Зафиксируем эту тройку. Заметим, что количество раз, которое &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; встречается в &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; при условии, что &amp;lt;tex&amp;gt; \delta(x, a) = y&amp;lt;/tex&amp;gt;, совпадает с числом удаленных из очереди пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;y \in C&amp;lt;/tex&amp;gt;. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит &amp;lt;tex&amp;gt;\log_2(|Q|)&amp;lt;/tex&amp;gt;. Просуммировав по всем &amp;lt;tex&amp;gt; x \in Q &amp;lt;/tex&amp;gt; и по всем &amp;lt;tex&amp;gt; a \in \Sigma&amp;lt;/tex&amp;gt; мы получим утверждение леммы.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement =&lt;br /&gt;
Время работы алгоритма Хопкрофта равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log(|Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof =&lt;br /&gt;
Оценим, сколько времени занимает каждая часть алгоритма:&lt;br /&gt;
&lt;br /&gt;
*Построение массива &amp;lt;tex&amp;gt;\mathtt{Inv}&amp;lt;/tex&amp;gt; занимает &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt; времени. &lt;br /&gt;
&lt;br /&gt;
*По [[#Лемма2 | второй лемме]] количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*Операции с множеством &amp;lt;tex&amp;gt;T'&amp;lt;/tex&amp;gt; и разбиение классов на подклассы требуют &amp;lt;tex&amp;gt;O(\sum(|\mathtt{Inverse}|))&amp;lt;/tex&amp;gt; времени. Но по [[#Лемма4 | лемме(4)]] &amp;lt;tex&amp;gt;\sum(|\mathtt{Inverse}|)&amp;lt;/tex&amp;gt; не превосходит &amp;lt;tex&amp;gt;|\Sigma| |Q| \log_2(|Q|)&amp;lt;/tex&amp;gt;, то есть данная часть алгоритма выполняется за &amp;lt;tex&amp;gt;O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем &amp;lt;tex&amp;gt;2 |Q| - 1&amp;lt;/tex&amp;gt; классов, из чего следует, что количество операций &amp;lt;tex&amp;gt;\mathtt{replace}&amp;lt;/tex&amp;gt; равно &amp;lt;tex&amp;gt;O(|\Sigma| |Q|)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Итого, получается, что время работы алгоритма Хопкрофта не превышает &amp;lt;tex&amp;gt; O(|\Sigma| |Q|) + O(|\Sigma| |Q|) + O(|\Sigma| |Q| \log_2(|Q|)) + O(|\Sigma| |Q|) = O(|\Sigma| |Q| \log_2(|Q|))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Альтернативная реализация ===&lt;br /&gt;
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).&lt;br /&gt;
&lt;br /&gt;
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Class}[r]&amp;lt;/tex&amp;gt; {{---}} индекс класса в &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;, которому принадлежит состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; {{---}} очередь из пар &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt;,&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Inv}[r][a]&amp;lt;/tex&amp;gt; {{---}} массив состояний, из которых есть ребра по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),&lt;br /&gt;
*&amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt; {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
     &amp;lt;tex&amp;gt;\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''insert''' &amp;lt;tex&amp;gt;\langle F,\ c \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle Q \setminus F,\ c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt; \mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''while''' &amp;lt;tex&amp;gt;\mathtt{Queue} \ne \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\leftarrow&amp;lt;/tex&amp;gt; '''pop from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       &amp;lt;tex&amp;gt;\mathtt{Involved} = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt;q \in C&amp;lt;/tex&amp;gt; '''and''' &amp;lt;tex&amp;gt;r \in \mathtt{Inv}[q][a]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;i = \mathtt{Class}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i] == \varnothing&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{Involved}[i] = \{\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; i \in \mathtt{Involved}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Перебираем ключи &amp;lt;tex&amp;gt;\mathtt{Involved}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| &amp;lt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''insert''' &amp;lt;tex&amp;gt;\{\}&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Создадим пустой класс в разбиении &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&amp;lt;/font&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;j = |\mathtt{P}|&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Запишем в &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; индекс нового класса&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{Involved}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''remove''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''from''' &amp;lt;tex&amp;gt;\mathtt{P}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''add''' &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;|\mathtt{P}[j]| &amp;gt; |\mathtt{P}[i]|&amp;lt;/tex&amp;gt;  &amp;lt;font color=darkgreen&amp;gt;//Парный класс должен быть меньшего размера&amp;lt;/font&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//swap за O(1) - просто переставить указатели&amp;lt;/font&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;r \in \mathtt{P}[j]&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Обновляем номера классов для вершин, у которых они изменились&amp;lt;/font&amp;gt;&lt;br /&gt;
               &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''for''' &amp;lt;tex&amp;gt;c \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''push''' &amp;lt;tex&amp;gt;\langle j, c \rangle&amp;lt;/tex&amp;gt; '''into''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\mathtt{P}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
&lt;br /&gt;
* [[Алгоритм Бржозовского]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)&lt;br /&gt;
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.&lt;br /&gt;
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.&lt;br /&gt;
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>94.25.228.16</name></author>	</entry>

	</feed>