<?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=Georgeee</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=Georgeee"/>
		<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/Georgeee"/>
		<updated>2026-06-11T16:46:37Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<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=44804</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=44804"/>
				<updated>2015-02-07T09:57:43Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &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;
  &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;
         '''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;
&lt;br /&gt;
&amp;lt;references/&amp;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>Georgeee</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=44214</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=44214"/>
				<updated>2015-01-11T21:48:36Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: sta&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;
  &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;
         '''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{Reversed}&amp;lt;/tex&amp;gt; {{---}} массив булеан, &amp;lt;tex&amp;gt;\mathtt{Reversed}[i] == &amp;lt;/tex&amp;gt; ''true'', если требуется добавлять состояния из &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt; в исходный класс &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, а не в новый класс (&amp;lt;tex&amp;gt;\mathtt{Twin}[i]&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;
*&amp;lt;tex&amp;gt;\mathtt{Used}&amp;lt;/tex&amp;gt; {{---}} массив булеан, &amp;lt;tex&amp;gt;\mathtt{Used}[r] == &amp;lt;/tex&amp;gt; ''true'', если состояние &amp;lt;tex&amp;gt;r&amp;lt;/tex&amp;gt; попало в &amp;lt;tex&amp;gt;\mathtt{Inverse}&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; '''in''' &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;
         &amp;lt;tex&amp;gt;\mathtt{Used}[r] = &amp;lt;/tex&amp;gt; ''true''&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;
             '''if''' &amp;lt;tex&amp;gt;\mathtt{Count}[i] &amp;gt; |\mathtt{P}[i]|/2&amp;lt;/tex&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;\mathtt{Reversed}[i] = &amp;lt;/tex&amp;gt; ''true''&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{Twin}[i] \neq 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
            '''if''' &amp;lt;tex&amp;gt;\lnot \mathtt{Reversed}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
              &amp;lt;tex&amp;gt;\mathtt{move}(r,\ i,\ \mathtt{Twin}[i])&amp;lt;/tex&amp;gt;&lt;br /&gt;
              &amp;lt;tex&amp;gt;\mathtt{Used}[r] = &amp;lt;/tex&amp;gt; ''false''&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{Twin}[i] \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
             '''if''' &amp;lt;tex&amp;gt;\mathtt{Reversed}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
               '''for''' &amp;lt;tex&amp;gt; r \in \mathtt{P}[i] &amp;lt;/tex&amp;gt;&lt;br /&gt;
                 '''if''' &amp;lt;tex&amp;gt;\lnot \mathtt{Used}[r]&amp;lt;/tex&amp;gt;&lt;br /&gt;
                    &amp;lt;tex&amp;gt;\mathtt{move}(r,\ i,\ \mathtt{Twin}[i])&amp;lt;/tex&amp;gt;&lt;br /&gt;
                 &amp;lt;tex&amp;gt;\mathtt{Used}[r] = &amp;lt;/tex&amp;gt; ''false''&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 \mathtt{Twin}[i], 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{Reversed}[i] = &amp;lt;/tex&amp;gt; ''false''&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;
  &amp;lt;tex&amp;gt;\mathtt{move}(r,\ i,\ j)&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;
     &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что массивы &amp;lt;tex&amp;gt;\mathtt{Count},\ \mathtt{Twin},\ \mathtt{Used},\ \mathtt{Reversed}&amp;lt;/tex&amp;gt; аллоцируются ровно один раз при инициализации алгоритма.&lt;br /&gt;
&lt;br /&gt;
Когда мы вычислили массив &amp;lt;tex&amp;gt;\mathtt{Count}&amp;lt;/tex&amp;gt;, мы смотрим, какая часть класса  &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;  больше и помечаем (&amp;lt;tex&amp;gt;\mathtt{Reversed}[i] = &amp;lt;/tex&amp;gt; ''true''), когда больше часть, состоящая из состояний множества &amp;lt;tex&amp;gt;\mathtt{Inverse}&amp;lt;/tex&amp;gt;. Далее, смотря на эту пометку, мы заполняем класс &amp;lt;tex&amp;gt;\mathtt{Twin}&amp;lt;/tex&amp;gt; либо состояниями из &amp;lt;tex&amp;gt;\mathtt{Inverse}&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;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;
|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; '''take any from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Взять любую пару из &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;, не удаляя (!)&amp;lt;/font&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;
                '''if''' &amp;lt;tex&amp;gt;|\mathtt{Involved}[i]| \leqslant |\mathtt{P}[i]|/2&amp;lt;/tex&amp;gt;&lt;br /&gt;
                   &amp;lt;tex&amp;gt;\mathtt{move}(r,\ i,\ j)&amp;lt;/tex&amp;gt;&lt;br /&gt;
                '''else'''&lt;br /&gt;
                   &amp;lt;tex&amp;gt;\mathtt{move}(r,\ j,\ i)&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;
       '''remove''' &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; '''from''' &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;
    &amp;lt;tex&amp;gt;\mathtt{move}(r,\ i,\ j)&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;
     &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&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;
&amp;lt;references/&amp;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>Georgeee</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=44198</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=44198"/>
				<updated>2015-01-11T19:51:10Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &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;
  &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;
         '''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;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2)&amp;lt;/tex&amp;gt; {{---}} функция, которая добавляет пары &amp;lt;tex&amp;gt;\langle R_1, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt; в очередь S.&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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R,\ R_1,\ R_2)&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;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R,\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow |\mathtt{P}[R_1]|&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow |\mathtt{P}[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;
         '''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; \mathrm{cnt1} \leqslant \mathrm{cnt2} &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;
&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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R,\ R_1,\ R_2)&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; '''in''' &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;&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;|\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;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Twin}[i] \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}[\mathtt{Twin}[i]]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            &amp;lt;tex&amp;gt;\mathtt{Class}[r] = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; j \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; \mathtt{Twin}[j] \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ j,\ \mathtt{Twin}[j])&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[j] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[j] = 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;
Стоит отметить, что массивы &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;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow |\mathtt{P}[R_1]|&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow |\mathtt{P}[R_2]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{swapClasses}(i,\ j)&amp;lt;/tex&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;//Поменять друг с другом содержимое индексов &amp;lt;tex&amp;gt;i,\ j&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;\mathtt{P}&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{P}[i]&amp;lt;/tex&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;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Class}[r] = i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоит пояснить, зачем требуется менять содержимое множеств (&amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt;). Почему нельзя просто проверить &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, и на основе этого добавить либо первое, либо второе? Ответ кроется в том, что де-факто мы создаем только один новый класс, старый класс (&amp;lt;tex&amp;gt;R_1&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_2| &amp;gt; |R_1| &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 R_1, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle R_2, 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;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt; вызывается только в том случае, когда &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, из чего следует &amp;lt;tex&amp;gt;\mathrm{cnt1} + \mathrm{cnt2} &amp;lt;= 2 \cdot |\mathtt{Inverse}| &amp;lt;/tex&amp;gt;, т.к. &amp;lt;tex&amp;gt;\mathrm{cnt2} = |\mathtt{Inverse}|&amp;lt;/tex&amp;gt;. Таким образом, вызов &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;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; '''take any from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Взять любую пару из &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;, не удаляя (!)&amp;lt;/font&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;
                &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ i,\ j)&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''remove''' &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; '''from''' &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;
В оригинальной статье &amp;lt;ref&amp;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]&amp;lt;/ref&amp;gt; использовалась дополнительная структура, которую мы обозначим, как &amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, в &amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a]&amp;lt;/tex&amp;gt; будем хранить множество состояний, из которых есть ребро по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; (аналогично &amp;lt;tex&amp;gt;Inv&amp;lt;/tex&amp;gt;, только для классов).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a] = \{ s\ |\ \mathtt{Class}[s] == C  \ \land \   \delta^{-1} (s, a) \neq \emptyset \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; реализуем так:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow  \mathtt{ClassInv}[R_1][c]&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow  \mathtt{ClassInv}[R_2][c]&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы&lt;br /&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
реализуются так:&lt;br /&gt;
&lt;br /&gt;
   '''for''' &amp;lt;tex&amp;gt;q \in \mathtt{ClassInv}[C][a]&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
Тогда время работы внутреннего цикла можно будет оценить как &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt;. А реализация &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; выбирает множество, на котором &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]|)&amp;lt;/tex&amp;gt; будет меньшим.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вместо [[Хеш-таблица | хэш-таблиц]] для хранения множеств (&amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, разбиение &amp;lt;tex&amp;gt;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;
&lt;br /&gt;
&amp;lt;references/&amp;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>Georgeee</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=44197</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=44197"/>
				<updated>2015-01-11T19:47:57Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &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;
  &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;
         '''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;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2)&amp;lt;/tex&amp;gt; {{---}} функция, которая добавляет пары &amp;lt;tex&amp;gt;\langle R_1, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt; в очередь S.&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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R,\ R_1,\ R_2)&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;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R,\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow |\mathtt{P}[R_1]|&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow |\mathtt{P}[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;
         '''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; \mathrm{cnt1} \leqslant \mathrm{cnt2} &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;
&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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R,\ R_1,\ R_2)&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; '''in''' &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;&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;|\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;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Twin}[i] \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}[\mathtt{Twin}[i]]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            &amp;lt;tex&amp;gt;\mathtt{Class}[r] = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; j \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; \mathtt{Twin}[j] \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ j,\ \mathtt{Twin}[j])&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[j] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[j] = 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;
Стоит отметить, что массивы &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;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow |\mathtt{P}[R_1]|&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow |\mathtt{P}[R_2]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{swapClasses}(i,\ j)&amp;lt;/tex&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;//Поменять друг с другом содержимое индексов &amp;lt;tex&amp;gt;i,\ j&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;\mathtt{P}&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{P}[i]&amp;lt;/tex&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;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Class}[r] = i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоит пояснить, зачем требуется менять содержимое множеств (&amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt;). Почему нельзя просто проверить &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, и на основе этого добавить либо первое, либо второе? Ответ кроется в том, что де-факто мы создаем только один новый класс, старый класс (&amp;lt;tex&amp;gt;R_1&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_2| &amp;gt; |R_1| &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 R_1, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle R_2, 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;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt; вызывается только в том случае, когда &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, из чего следует &amp;lt;tex&amp;gt;\mathrm{cnt1} + \mathrm{cnt2} &amp;lt;= 2 \cdot |\mathtt{Inverse}| &amp;lt;/tex&amp;gt;, т.к. &amp;lt;tex&amp;gt;\mathrm{cnt2} = |\mathtt{Inverse}|&amp;lt;/tex&amp;gt;. Таким образом, вызов &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;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; '''take any from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Взять любую пару из &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;, не удаляя (!)&amp;lt;/font&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 = &amp;lt;/tex&amp;gt; |\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;
                &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ i,\ j)&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''remove''' &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; '''from''' &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;
В оригинальной статье &amp;lt;ref&amp;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]&amp;lt;/ref&amp;gt; использовалась дополнительная структура, которую мы обозначим, как &amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, в &amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a]&amp;lt;/tex&amp;gt; будем хранить множество состояний, из которых есть ребро по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; (аналогично &amp;lt;tex&amp;gt;Inv&amp;lt;/tex&amp;gt;, только для классов).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a] = \{ s\ |\ \mathtt{Class}[s] == C  \ \land \   \delta^{-1} (s, a) \neq \emptyset \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; реализуем так:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow  \mathtt{ClassInv}[R_1][c]&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow  \mathtt{ClassInv}[R_2][c]&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы&lt;br /&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
реализуются так:&lt;br /&gt;
&lt;br /&gt;
   '''for''' &amp;lt;tex&amp;gt;q \in \mathtt{ClassInv}[C][a]&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
Тогда время работы внутреннего цикла можно будет оценить как &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt;. А реализация &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; выбирает множество, на котором &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]|)&amp;lt;/tex&amp;gt; будет меньшим.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вместо [[Хеш-таблица | хэш-таблиц]] для хранения множеств (&amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, разбиение &amp;lt;tex&amp;gt;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;
&lt;br /&gt;
&amp;lt;references/&amp;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>Georgeee</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=44195</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=44195"/>
				<updated>2015-01-11T19:38:32Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &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;
  &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;
         '''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;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2)&amp;lt;/tex&amp;gt; {{---}} функция, которая добавляет пары &amp;lt;tex&amp;gt;\langle R_1, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\langle R_2, \forall c \in \Sigma \rangle&amp;lt;/tex&amp;gt; в очередь S.&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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2)&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;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;
         &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(S,\ R_1,\ R_2)&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; '''in''' &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;&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;|\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;
         '''if''' &amp;lt;tex&amp;gt;\mathtt{Twin}[i] \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}[\mathtt{Twin}[i]]&amp;lt;/tex&amp;gt;&lt;br /&gt;
            &amp;lt;tex&amp;gt;\mathtt{Class}[r] = \mathtt{Twin}[i]&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''for''' &amp;lt;tex&amp;gt; j \in \mathtt{Involved}&amp;lt;/tex&amp;gt;&lt;br /&gt;
         '''if''' &amp;lt;tex&amp;gt; \mathtt{Twin}[j] \neq 0 &amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ j,\ \mathtt{Twin}[j])&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Count}[j] = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Twin}[j] = 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;
Стоит отметить, что массивы &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;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow |\mathtt{P}[R_1]|&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow |\mathtt{P}[R_2]|&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{swapClasses}(i,\ j)&amp;lt;/tex&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;//Поменять друг с другом содержимое индексов &amp;lt;tex&amp;gt;i,\ j&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;\mathtt{P}&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{P}[i]&amp;lt;/tex&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;r&amp;lt;/tex&amp;gt; '''in''' &amp;lt;tex&amp;gt;\mathtt{P}[j]&amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{Class}[r] = i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоит пояснить, зачем требуется менять содержимое множеств (&amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt;). Почему нельзя просто проверить &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, и на основе этого добавить либо первое, либо второе? Ответ кроется в том, что де-факто мы создаем только один новый класс, старый класс (&amp;lt;tex&amp;gt;R_1&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_2| &amp;gt; |R_1| &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 R_1, c \rangle&amp;lt;/tex&amp;gt; уже была в очереди, то мы добавим её &amp;quot;вторую половинку&amp;quot; &amp;lt;tex&amp;gt;\langle R_2, 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;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;gt; вызывается только в том случае, когда &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;, из чего следует &amp;lt;tex&amp;gt;\mathrm{cnt1} + \mathrm{cnt2} &amp;lt;= 2 \cdot |\mathtt{Inverse}| &amp;lt;/tex&amp;gt;, т.к. &amp;lt;tex&amp;gt;\mathrm{cnt2} = |\mathtt{Inverse}|&amp;lt;/tex&amp;gt;. Таким образом, вызов &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ R_2)&amp;lt;/tex&amp;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; '''take any from''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt; &amp;lt;font color=darkgreen&amp;gt;//Взять любую пару из &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;, не удаляя (!)&amp;lt;/font&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 = &amp;lt;/tex&amp;gt; |\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;
                &amp;lt;tex&amp;gt;\mathtt{Class}[r] = j&amp;lt;/tex&amp;gt;&lt;br /&gt;
             &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ i,\ j)&amp;lt;/tex&amp;gt;&lt;br /&gt;
       '''remove''' &amp;lt;tex&amp;gt;\langle C,\ a \rangle&amp;lt;/tex&amp;gt; '''from''' &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;
В оригинальной статье &amp;lt;ref&amp;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]&amp;lt;/ref&amp;gt; использовалась дополнительная структура, которую мы обозначим, как &amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, в &amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a]&amp;lt;/tex&amp;gt; будем хранить множество состояний, из которых есть ребро по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; в состояние &amp;lt;tex&amp;gt;C&amp;lt;/tex&amp;gt; (аналогично &amp;lt;tex&amp;gt;Inv&amp;lt;/tex&amp;gt;, только для классов).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{ClassInv}[C][a] = \{ s\ |\ \mathtt{Class}[s] == C  \ \land \   \delta^{-1} (s, a) \neq \emptyset \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; реализуем так:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}(\mathtt{Queue},\ R_1,\ R_2)&amp;lt;/tex&amp;gt;:&lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt1}  \leftarrow  \mathtt{ClassInv}[R_1][c]&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;\mathrm{cnt2}  \leftarrow  \mathtt{ClassInv}[R_2][c]&amp;lt;/tex&amp;gt;&lt;br /&gt;
      '''if''' &amp;lt;tex&amp;gt; \mathrm{cnt1} \leqslant \mathrm{cnt2} &amp;lt;/tex&amp;gt;&lt;br /&gt;
         &amp;lt;tex&amp;gt;\mathtt{swapClasses}(R_1,\ 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;
         '''push''' &amp;lt;tex&amp;gt;\langle R_2, c \rangle&amp;lt;/tex&amp;gt; '''to''' &amp;lt;tex&amp;gt;\mathtt{Queue}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы&lt;br /&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
реализуются так:&lt;br /&gt;
&lt;br /&gt;
   '''for''' &amp;lt;tex&amp;gt;q \in \mathtt{ClassInv}[C][a]&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;
      (...)&lt;br /&gt;
&lt;br /&gt;
Тогда время работы внутреннего цикла можно будет оценить как &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]| + |\mathtt{Inverse}|)&amp;lt;/tex&amp;gt;. А реализация &amp;lt;tex&amp;gt;\mathtt{pushSetsToQueue}&amp;lt;/tex&amp;gt; выбирает множество, на котором &amp;lt;tex&amp;gt;O(|\mathtt{ClassInv}[C][a]|)&amp;lt;/tex&amp;gt; будет меньшим.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вместо [[Хеш-таблица | хэш-таблиц]] для хранения множеств (&amp;lt;tex&amp;gt;\mathtt{ClassInv}&amp;lt;/tex&amp;gt;, разбиение &amp;lt;tex&amp;gt;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;
&lt;br /&gt;
&amp;lt;references/&amp;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>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35507</id>
		<title>Паросочетания: основные определения, теорема о максимальном паросочетании и дополняющих цепях</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35507"/>
				<updated>2014-01-10T17:46:22Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Обобщения задачи */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются (у каждого мужчины своя функция оценки)&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются (у каждой женщины своя функция оценки)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85#matching_def паросочетание] в МЖ. Пара A-b называется неустойчивой (unstable pair), если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание (stable matching) - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American Mathematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Асимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Покажем, как реализовать внутреннюю часть цикла while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; с предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, тогда итоговая ассимптотика очевидно будет &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Все мужчины и женщины нумеруются числами от 1 до N.&lt;br /&gt;
&lt;br /&gt;
Будем поддерживать два одномерных массива: wife, husband (wife[m] - id жены мужчины m или 0, если мужчина не женат; husband[w] - id мужа женщины w или 0, если женщина не замужем)&lt;br /&gt;
&lt;br /&gt;
Также нам нужен список свободных мужчин free, для этого можно использовать и, например, очередь или стек.&lt;br /&gt;
&lt;br /&gt;
Предпочтения каждого мужчины будем хранить списком, в котором женщины отсортированы от самой привлекательной к наименее привлекательной (такой список можно построить из любого другого представления не более чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, при необходимости сортировать можно сортировкой подсчетом). Соответствующий двумерный массив обозначим за mp (men preference).&lt;br /&gt;
&lt;br /&gt;
Для навигации по спискам предпочтений нам понадобится массив count, count[m] - количество женщин, которым делал предложение мужчина m.&lt;br /&gt;
&lt;br /&gt;
Для женщин создадим массив wp (women preference), который аналогичен инверсии аналогичной структуры для мужчин. В частности, wp[w][m] - индекс мужчины m в списке предпочтений женщины w. Очевидно, такой массив может быть построен за время не более, чем &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Тогда цикл while может быть реализован следующим образом:&lt;br /&gt;
&lt;br /&gt;
   '''while''' not free.isEmpty()&lt;br /&gt;
      m = free.pop()&lt;br /&gt;
      w = mp[m][count[m]++]&lt;br /&gt;
      m' = husband[w]&lt;br /&gt;
      '''if''' m' == 0&lt;br /&gt;
         husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
      '''else if''' wp[w][m] &amp;lt; wp[w][m']&lt;br /&gt;
         husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
         wife[m'] = 0&lt;br /&gt;
         free.push(m')&lt;br /&gt;
      '''else'''&lt;br /&gt;
         free.push(m)&lt;br /&gt;
&lt;br /&gt;
=== Анализ полученного алгоритмом паросочетания ===&lt;br /&gt;
&lt;br /&gt;
Агоритм Гейла-Шепли гарантирует, что будет найдено некоторое решение задачи. Но решений может быть более одного. Зададимся вопросом, какими свойствами обладает решение, найденное алгоритмом.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. [http://ru.wikipedia.org/wiki/Задача_о_соседях_по_комнате Задача о соседях по комнате])&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидно сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;br /&gt;
&lt;br /&gt;
== Ссылки ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf Stable matching, Prinston lecture's presentation]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Stable_marriage_problem Stable marriage problem]&lt;br /&gt;
* [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BC%D0%B0%D1%80%D1%8C%D1%8F%D0%B6%D0%B5 Задача о марьяже]&lt;br /&gt;
* [http://ge.tt/api/1/files/4LU3zaD1/0/blob?download Устойчивость супружеских пар и другие комбинаторные задачи (Статья Дональда Кнута)]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Задача о паросочетании]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35506</id>
		<title>Паросочетания: основные определения, теорема о максимальном паросочетании и дополняющих цепях</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35506"/>
				<updated>2014-01-10T17:37:03Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Асимптотика алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются (у каждого мужчины своя функция оценки)&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются (у каждой женщины своя функция оценки)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85#matching_def паросочетание] в МЖ. Пара A-b называется неустойчивой (unstable pair), если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание (stable matching) - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American Mathematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Асимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Покажем, как реализовать внутреннюю часть цикла while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; с предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, тогда итоговая ассимптотика очевидно будет &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Все мужчины и женщины нумеруются числами от 1 до N.&lt;br /&gt;
&lt;br /&gt;
Будем поддерживать два одномерных массива: wife, husband (wife[m] - id жены мужчины m или 0, если мужчина не женат; husband[w] - id мужа женщины w или 0, если женщина не замужем)&lt;br /&gt;
&lt;br /&gt;
Также нам нужен список свободных мужчин free, для этого можно использовать и, например, очередь или стек.&lt;br /&gt;
&lt;br /&gt;
Предпочтения каждого мужчины будем хранить списком, в котором женщины отсортированы от самой привлекательной к наименее привлекательной (такой список можно построить из любого другого представления не более чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, при необходимости сортировать можно сортировкой подсчетом). Соответствующий двумерный массив обозначим за mp (men preference).&lt;br /&gt;
&lt;br /&gt;
Для навигации по спискам предпочтений нам понадобится массив count, count[m] - количество женщин, которым делал предложение мужчина m.&lt;br /&gt;
&lt;br /&gt;
Для женщин создадим массив wp (women preference), который аналогичен инверсии аналогичной структуры для мужчин. В частности, wp[w][m] - индекс мужчины m в списке предпочтений женщины w. Очевидно, такой массив может быть построен за время не более, чем &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Тогда цикл while может быть реализован следующим образом:&lt;br /&gt;
&lt;br /&gt;
   '''while''' not free.isEmpty()&lt;br /&gt;
      m = free.pop()&lt;br /&gt;
      w = mp[m][count[m]++]&lt;br /&gt;
      m' = husband[w]&lt;br /&gt;
      '''if''' m' == 0&lt;br /&gt;
         husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
      '''else if''' wp[w][m] &amp;lt; wp[w][m']&lt;br /&gt;
         husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
         wife[m'] = 0&lt;br /&gt;
         free.push(m')&lt;br /&gt;
      '''else'''&lt;br /&gt;
         free.push(m)&lt;br /&gt;
&lt;br /&gt;
=== Анализ полученного алгоритмом паросочетания ===&lt;br /&gt;
&lt;br /&gt;
Агоритм Гейла-Шепли гарантирует, что будет найдено некоторое решение задачи. Но решений может быть более одного. Зададимся вопросом, какими свойствами обладает решение, найденное алгоритмом.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. [[http://ru.wikipedia.org/wiki/Задача_о_соседях_по_комнате|Задача о соседях по комнате]])&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидно сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;br /&gt;
&lt;br /&gt;
== Ссылки ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf Stable matching, Prinston lecture's presentation]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Stable_marriage_problem Stable marriage problem]&lt;br /&gt;
* [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BC%D0%B0%D1%80%D1%8C%D1%8F%D0%B6%D0%B5 Задача о марьяже]&lt;br /&gt;
* [http://ge.tt/api/1/files/4LU3zaD1/0/blob?download Устойчивость супружеских пар и другие комбинаторные задачи (Статья Дональда Кнута)]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Задача о паросочетании]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35505</id>
		<title>Паросочетания: основные определения, теорема о максимальном паросочетании и дополняющих цепях</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35505"/>
				<updated>2014-01-10T17:36:18Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются (у каждого мужчины своя функция оценки)&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются (у каждой женщины своя функция оценки)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85#matching_def паросочетание] в МЖ. Пара A-b называется неустойчивой (unstable pair), если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание (stable matching) - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American Mathematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Асимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Покажем, как реализовать внутреннюю часть цикла while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; с предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, тогда итоговая ассимптотика очевидно будет &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Все мужчины и женщины нумеруются числами от 1 до N.&lt;br /&gt;
&lt;br /&gt;
Будем поддерживать два одномерных массива: wife, husband (wife[m] - id жены мужчины m или 0, если мужчина не женат; husband[w] - id мужа женщины w или 0, если женщина не замужем)&lt;br /&gt;
&lt;br /&gt;
Также нам нужен список свободных мужчин free, для этого можно использовать и, например, очередь или стек.&lt;br /&gt;
&lt;br /&gt;
Предпочтения каждого мужчины будем хранить списком, в котором женщины отсортированы от самой привлекательной к наименее привлекательной (такой список можно построить из любого другого представления не более чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, при необходимости сортировать можно сортировкой подсчетом). Соответствующий двумерный массив обозначим за mp (men preference).&lt;br /&gt;
&lt;br /&gt;
Для навигации по спискам предпочтений нам понадобится массив count, count[m] - количество женщин, которым делал предложение мужчина m.&lt;br /&gt;
&lt;br /&gt;
Для женщин создадим массив wp (women preference), который аналогичен инверсии аналогичной структуры для мужчин. В частности, wp[w][m] - индекс мужчины m в списке предпочтений женщины w. Очевидно, такой массив может быть построен за время не более, чем &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Тогда цикл while может быть реализован следующим образом:&lt;br /&gt;
&lt;br /&gt;
	'''while''' not free.isEmpty()&lt;br /&gt;
      m = free.pop()&lt;br /&gt;
      w = mp[m][count[m]++]&lt;br /&gt;
      m' = husband[w]&lt;br /&gt;
      '''if''' m' == 0&lt;br /&gt;
         husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
      '''else if''' wp[w][m] &amp;lt; wp[w][m']&lt;br /&gt;
	     husband[w] = m&lt;br /&gt;
         wife[m] = w&lt;br /&gt;
         wife[m'] = 0&lt;br /&gt;
         free.push(m')&lt;br /&gt;
      '''else'''&lt;br /&gt;
         free.push(m)&lt;br /&gt;
&lt;br /&gt;
=== Анализ полученного алгоритмом паросочетания ===&lt;br /&gt;
&lt;br /&gt;
Агоритм Гейла-Шепли гарантирует, что будет найдено некоторое решение задачи. Но решений может быть более одного. Зададимся вопросом, какими свойствами обладает решение, найденное алгоритмом.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. [[http://ru.wikipedia.org/wiki/Задача_о_соседях_по_комнате|Задача о соседях по комнате]])&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидно сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;br /&gt;
&lt;br /&gt;
== Ссылки ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf Stable matching, Prinston lecture's presentation]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Stable_marriage_problem Stable marriage problem]&lt;br /&gt;
* [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BC%D0%B0%D1%80%D1%8C%D1%8F%D0%B6%D0%B5 Задача о марьяже]&lt;br /&gt;
* [http://ge.tt/api/1/files/4LU3zaD1/0/blob?download Устойчивость супружеских пар и другие комбинаторные задачи (Статья Дональда Кнута)]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Задача о паросочетании]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35496</id>
		<title>Задача об устойчивом паросочетании</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35496"/>
				<updated>2014-01-10T12:06:53Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются (у каждого мужчины своя функция оценки)&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются (у каждой женщины своя функция оценки)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85#matching_def паросочетание] в МЖ. Пара A-b называется неустойчивой (unstable pair), если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание (stable matching) - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American Mathematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Асимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Не представляет проблемы реализовать внутренний цикл while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; (с предварительным предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;). Таким образом, итоговая асимптотика составляет &amp;lt;tex&amp;gt;O(n^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;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. [[http://ru.wikipedia.org/wiki/Задача_о_соседях_по_комнате|Задача о соседях по комнате]])&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидно сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;br /&gt;
&lt;br /&gt;
== Ссылки ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf Stable matching, Prinston lecture's presentation]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Stable_marriage_problem Stable marriage problem]&lt;br /&gt;
* [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BC%D0%B0%D1%80%D1%8C%D1%8F%D0%B6%D0%B5 Задача о марьяже]&lt;br /&gt;
* [http://ge.tt/api/1/files/4LU3zaD1/0/blob?download Устойчивость супружеских пар и другие комбинаторные задачи (Статья Дональда Кнута)]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Задача о паросочетании]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35495</id>
		<title>Задача об устойчивом паросочетании</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35495"/>
				<updated>2014-01-10T08:56:03Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются (у каждого мужчины своя функция оценки)&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются (у каждой женщины своя функция оценки)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85#matching_def паросочетание] в МЖ. Пара A-b называется неустойчивой (unstable pair), если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание (stable matching) - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American Mathematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Асимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Не представляет проблемы реализовать внутренний цикл while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; (с предварительным предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;). Таким образом, итоговая асимптотика составляет &amp;lt;tex&amp;gt;O(n^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;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. [[http://ru.wikipedia.org/wiki/Задача_о_соседях_по_комнате|Задача о соседях по комнате]])&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидно сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;br /&gt;
&lt;br /&gt;
== Ссылки ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf Stable matching, Prinston lecture's presentation]&lt;br /&gt;
* [http://en.wikipedia.org/wiki/Stable_marriage_problem Stable marriage problem]&lt;br /&gt;
* [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BC%D0%B0%D1%80%D1%8C%D1%8F%D0%B6%D0%B5 Задача о марьяже]&lt;br /&gt;
* [http://ge.tt/api/1/files/4LU3zaD1/0/blob?download Устойчивость супружеских пар и другие комбинаторные задачи (Статья Дональда Кнута)]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35494</id>
		<title>Паросочетания: основные определения, теорема о максимальном паросочетании и дополняющих цепях</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D1%8F:_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F,_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8_%D0%B8_%D0%B4%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%8E%D1%89%D0%B8%D1%85_%D1%86%D0%B5%D0%BF%D1%8F%D1%85&amp;diff=35494"/>
				<updated>2014-01-10T08:29:11Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Паросочетание в двудольном графе */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Паросочетание в двудольном графе==&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=matching_def&lt;br /&gt;
|definition= '''Паросочетание''' &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; в двудольном графе — произвольное множество ребер двудольного графа, такое что никакие два ребра не имеют общей вершины.}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= Вершины двудольного графа, инцидентные ребрам паросочетания &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt;, называются '''покрытыми''', а неинцидентные — '''свободными'''.}} &lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= '''Чередующаяся цепь''' — путь в двудольном графе, для любых двух соседних ребер которого верно, что одно из них принадлежит паросочетанию &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt;, а другое нет.}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= '''Дополняющая цепь''' — чередующаяся цепь, у которой оба конца свободны.}}&lt;br /&gt;
&lt;br /&gt;
== Теорема о максимальном паросочетании и дополняющих цепях ==&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|id=theorem1&lt;br /&gt;
|statement=&lt;br /&gt;
Паросочетание &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; в двудольном графе &amp;lt;tex&amp;gt;G&amp;lt;/tex&amp;gt; является максимальным тогда и только тогда, когда в &amp;lt;tex&amp;gt;G&amp;lt;/tex&amp;gt; нет дополняющей цепи.&lt;br /&gt;
|proof=&lt;br /&gt;
&amp;lt;tex&amp;gt;\Rightarrow&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть в двудольном графе &amp;lt;tex&amp;gt;G&amp;lt;/tex&amp;gt; с максимальным паросочетанием &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; существует дополняющая цепь. Тогда пройдя по ней и заменив вдоль нее все ребра, входящие в паросочетание, на невходящие и наоборот, мы получим большее паросочетание. То есть &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не являлось максимальным. Противоречие.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\Leftarrow&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В доказательстве используются несколько новых понятий:&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= '''Увеличивающая цепь''' — чередующаяся цепь, у которой оба конца свободны.}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= '''Уменьшающая цепь''' — чередующаяся цепь, у которой оба конца покрыты.}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition= '''Сбалансированная цепь''' — чередующаяся цепь, у которой один конец свободен, а другой покрыт}}&lt;br /&gt;
&lt;br /&gt;
Рассмотрим паросочетание &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; в графе &amp;lt;tex&amp;gt;G&amp;lt;/tex&amp;gt; и предположим, что &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; - не наибольшее. Докажем, что тогда имеется увеличивающая цепь относительно &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt;. Пусть &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt; - другое паросочетание и &amp;lt;tex&amp;gt;|M'|&amp;gt;|M|&amp;lt;/tex&amp;gt;. Рассмотрим подграф &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; графа &amp;lt;tex&amp;gt;G&amp;lt;/tex&amp;gt;, образованный теми ребрами, которые входят в одно и только в одно из паросочетаний &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt;. Иначе говоря, множеством ребер графа &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; является симметрическая разность &amp;lt;tex&amp;gt;M\oplus M'&amp;lt;/tex&amp;gt;. В графе &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; каждая вершина инцидентна не более чем двум ребрам (одному из &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; и одному из &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt; ), т.е. имеет степень не более двух. В таком графе каждая компонента связности - путь или цикл. В каждом из этих путей и циклов чередуются ребра из &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt;. Так как &amp;lt;tex&amp;gt;|M'|&amp;gt;|M|&amp;lt;/tex&amp;gt;, имеется компонента, в которой ребер из &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt; содержится больше, чем ребер из &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt;. Это может быть только путь, у которого оба концевых ребра принадлежат &amp;lt;tex&amp;gt;M'&amp;lt;/tex&amp;gt;. Заметим, что относительно &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; этот путь является увеличивающей (дополняющей) цепью.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Литература==&lt;br /&gt;
*  Асанов М. О., Баранский В. А., Расин В. В. — Дискретная математика: Графы, матроиды, алгоритмы. '''ISBN 978-5-8114-1068-2'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Задача о паросочетании]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35489</id>
		<title>Задача об устойчивом паросочетании</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35489"/>
				<updated>2014-01-09T19:29:30Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Основная задача */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое паросочетание в МЖ. Пара A-b называется неустойчивой, если:&lt;br /&gt;
# В паросочетании есть пары A-a и B-b (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American texematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ассимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Не представляет проблемы реализовать внутренний цикл while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; (с предварительным предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;). Таким образом, итоговая асимптотика составляет &amp;lt;tex&amp;gt;O(n^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;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. Задача о соседях по комнате)&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидна сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35488</id>
		<title>Задача об устойчивом паросочетании</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35488"/>
				<updated>2014-01-09T19:26:54Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Ассимптотика алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое паросочетание в МЖ. Пара Ab называется неустойчивой, если:&lt;br /&gt;
# В паросочетании есть пары Aa и Bb (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American texematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ассимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Не представляет проблемы реализовать внутренний цикл while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; (с предварительным предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;). Таким образом, итоговая асимптотика составляет &amp;lt;tex&amp;gt;O(n^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;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. Задача о соседях по комнате)&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидна сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35485</id>
		<title>Задача об устойчивом паросочетании</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE%D0%B1_%D1%83%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D0%BC_%D0%BF%D0%B0%D1%80%D0%BE%D1%81%D0%BE%D1%87%D0%B5%D1%82%D0%B0%D0%BD%D0%B8%D0%B8&amp;diff=35485"/>
				<updated>2014-01-09T19:22:49Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: Новый конспект&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Основная задача ==&lt;br /&gt;
&lt;br /&gt;
Есть N мужчин и N женщин. Они обладают следующими особенностями:&lt;br /&gt;
# Каждый человек оценивает лишь людей противоположного пола (все гетеросексуальны)&lt;br /&gt;
# Каждый мужчина может отсортировать женщин от &amp;quot;наименее привлекательной&amp;quot; к &amp;quot;наиболее привлекательной&amp;quot;, причем его предпочтения не меняются&lt;br /&gt;
# Каждая женщина может отсортировать мужчин от &amp;quot;наименее привлекательного&amp;quot; к &amp;quot;наиболее привлекательному&amp;quot;, причем её предпочтения не меняются&lt;br /&gt;
&lt;br /&gt;
Очевидным образом по такому определению строится полный двудольный граф (левая доля - мужчины, правая - женщины), назовем его МЖ.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим некоторое паросочетание в МЖ. Пара Ab называется неустойчивой, если:&lt;br /&gt;
# В паросочетании есть пары Aa и Bb (A женат на a, B женат на b)&lt;br /&gt;
# A считает b привлекательней, чем a&lt;br /&gt;
# b считает A привлекательней, чем B&lt;br /&gt;
(неформально это означает потенциальную возможность измены)&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Устойчивое паросочетание - паросочетание без неустойчивых пар.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Задача заключается в нахождении полного устойчивого паросочетания по данным спискам предпочтений.&lt;br /&gt;
&lt;br /&gt;
== Агоритм Гейла-Шепли ==&lt;br /&gt;
&lt;br /&gt;
Решение задачи было описано в 1962 году математиками Девидом Гейлом (Университета Брауна) и Ллойдом Шепли (Принстонский университет) в статье «Поступление в колледж и стабильность браков» (College admissions and the stability of marriage) в журнале American texematical Monthly. Набор правил, следование которым всегда приводит к образованию стабильных пар, получил название алгоритма Гейла-Шепли или «алгоритма отложенного согласия» (алгоритм предложи-и-откажи).&lt;br /&gt;
&lt;br /&gt;
=== Интуитивное описание ===&lt;br /&gt;
&lt;br /&gt;
# мужчины делают предложение наиболее предпочитаемой женщине;&lt;br /&gt;
# каждая женщина из всех поступивших предложений выбирает наилучшее и отвечает на него «может быть» (помолвка), на все остальные отвечает «нет» (отказ)&lt;br /&gt;
# мужчины, получившие отказ, обращаются к следующей женщине из своего списка предпочтений, мужчины, получившие ответ «может быть», ничего не делают;&lt;br /&gt;
# если женщине пришло предложение лучше предыдущего, то она прежнему претенденту (которому ранее сказала «может быть») говорит «нет», а новому претенденту говорит «может быть»;&lt;br /&gt;
# шаги 1-4 повторяются, пока у всех мужчин не исчерпается список предложений, в этот момент женщины отвечают «да» на те предложения «может быть», которые у них есть в настоящий момент.&lt;br /&gt;
&lt;br /&gt;
=== Описание в псевдокоде ===&lt;br /&gt;
&lt;br /&gt;
   Изначально все мужчины и все женщины не женаты (не замужем)&lt;br /&gt;
   '''while''' Существует m &amp;lt;- некоторый свободный мужчина, не делавший предложения всем женщинам&lt;br /&gt;
      w &amp;lt;- первая женщина из списка m, которой m еще не делал предложения&lt;br /&gt;
      '''if''' w свободна&lt;br /&gt;
         помечаем m и w помолвленными&lt;br /&gt;
      '''else if''' w предпочитает m своему &amp;quot;текущему&amp;quot; жениху m'&lt;br /&gt;
         помечаем m и w помолвленными, m' - свободным&lt;br /&gt;
      '''else'''&lt;br /&gt;
         w отказывает m&lt;br /&gt;
&lt;br /&gt;
=== Доказательство корректности ===&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation1&lt;br /&gt;
|about=Наблюдение 1&lt;br /&gt;
|statement=Мужчины делают предложения женщинам в порядке убывания симпатии&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Утверждение&lt;br /&gt;
|id=observation2&lt;br /&gt;
|about=Наблюдение 2&lt;br /&gt;
|statement=Как только женщина была помолвлена, она не может стать непомолвленной, она может только улучшить свой выбор (сказать «может быть» более предпочтительному кандидату)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Для начала покажем, что алгоритм завершит свою работу.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=Лемма 1&lt;br /&gt;
|statement=&lt;br /&gt;
	Алгоритм завершается после максимум n^2 итераций цикла '''while'''&lt;br /&gt;
|proof=&lt;br /&gt;
	На каждой итерации мужчина делает предложение очередной женщине. Но всего может быть не более n^2 предложений.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Теперь покажем, что по завершении алгоритма задача будет решена.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=Лемма 2&lt;br /&gt;
|statement=&lt;br /&gt;
	Все мужчины и женщины будут заняты.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим, что некоторый мужчина, Ян, не женат по завершении алгоритма.&lt;br /&gt;
&lt;br /&gt;
	2. Тогда некоторая женщина, Антонина не замужем&lt;br /&gt;
&lt;br /&gt;
	3. По [[#observation2|наблюдению 2]], Антонине никто не делал предложения&lt;br /&gt;
&lt;br /&gt;
	4. Но Ян сделал предложения всем женщинам, т.к. он остался не женат&lt;br /&gt;
&lt;br /&gt;
	5. Получаем противоречие&lt;br /&gt;
	&lt;br /&gt;
	6. Аналогичные утверждения можно повторить и отталкиваясь от того, что не занята некоторая девушка, поэтому предложение доказано&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=Лемма 3&lt;br /&gt;
|statement=&lt;br /&gt;
	Нет неустойчивых пар.&lt;br /&gt;
|proof=&lt;br /&gt;
	1. Предположим A-b (A, B - мужчины; a, b - женщины; A женат на a, B женат на b) - нестабильная пара в паросочетнаии, найденном алгоритмом Гейла-Шепли&lt;br /&gt;
&lt;br /&gt;
	2. Рассмотрим два случая:&lt;br /&gt;
&lt;br /&gt;
	2.1.   A не делал предложения b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A находит a более привлекательной, чем b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
&lt;br /&gt;
	2.2.  A делал предложение b&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b отказала A (сразу или на одной из последующих итераций)&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; b находит B более привлекательным, чем A&lt;br /&gt;
&lt;br /&gt;
		 =&amp;gt; A-b - устойчивая пара&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ассимптотика алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Не представляет проблемы реализовать внутренний цикл while за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; (с предварительным предпроцессингом не более, чем за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;). Таким образом, итоговая асимптотика составляет O(n^2).&lt;br /&gt;
&lt;br /&gt;
=== Анализ полученного алгоритмом паросочетания ===&lt;br /&gt;
&lt;br /&gt;
Агоритм Гейла-Шепли гарантирует, что будет найдено некоторое решение задачи. Но решений может быть более одного. Зададимся вопросом, какими свойствами обладает решение, найденное алгоритмом.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma4&lt;br /&gt;
|about=Лемма 4 (man-optimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наилучшее для мужчин (каждый мужчина получает в жены женщину, наилучшую из всех возможных при условии корректности решения).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma5&lt;br /&gt;
|about=Лемма 5 (woman-pessimality)&lt;br /&gt;
|statement=&lt;br /&gt;
	Из всех возможных решений алгоритмом Гейла-Шепли будет найдено решение, наихудшее для женщин.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Эти две леммы оставим без доказательства, интересющиеся могут обратится к документу http://www.cs.princeton.edu/courses/archive/spring05/cos423/lectures/01stable-matching.pdf (с.5)&lt;br /&gt;
&lt;br /&gt;
== Обобщения задачи ==&lt;br /&gt;
&lt;br /&gt;
Интересно, что данная задача не всегда имеет решение, если допустить однополые пары (устойчивого паросочетания может не быть). (см. Задача о соседях по комнате)&lt;br /&gt;
&lt;br /&gt;
Случай же, когда у нас есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; мужчин и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; женщин (&amp;lt;tex&amp;gt;N \neq M&amp;lt;/tex&amp;gt;) легко сводится к описанной выше задаче. Рассмотрим &amp;lt;tex&amp;gt;M &amp;gt; N&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;M &amp;lt; N&amp;lt;/tex&amp;gt; аналогично). Добавим &amp;lt;tex&amp;gt;M - N&amp;lt;/tex&amp;gt; фиктивных мужчин, которые являются наименее привлекательными с точки зрения каждой из женщин. Тогда если в найденном алгоритмом Гейла-Шепли паросочетании некоторая женщина будет замужем за таким фиктивным мужчиной, это будет де-факто означать, что она осталась не замужем.&lt;br /&gt;
&lt;br /&gt;
Также интересна задача о выборе учебного заведения: вместо множества мужчин введем множество университетов, а вместо множества женщин - множество кандидатов, подающих заявления на поступление. Причем в каждом университете есть квота на количество студентов, которое университет может принять. Задача очевидна сводится к основной добавлением &amp;lt;tex&amp;gt;(K-1)&amp;lt;/tex&amp;gt; &amp;quot;филиалов&amp;quot; для каждого университета (&amp;lt;tex&amp;gt;K&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;
&lt;br /&gt;
Решение данной задачи было отмечено при вручении Нобелевской премии по экономике в 2012 году за «теорию стабильного распределения и практическое применение рыночных моделей». Её получили один из создателей алгоритма, Ллойд Шепли, а также Элвин Рот, во многом развивший исследования Ллойда Шепли и Дэвида Гейла. Сам Гейл не был удостоен премии, вероятно, лишь в силу того, что умер в 2008 году.&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=28462</id>
		<title>Задача о редакционном расстоянии, алгоритм Вагнера-Фишера</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=28462"/>
				<updated>2012-12-26T18:25:24Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: /* Рекурсивный алгоритм */ Псевдокод - формат&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Расстояние Левенштейна''' (также '''редакционное расстояние''' или '''дистанция редактирования''') между двумя строками в теории информации и компьютерной лингвистике — это минимальное количество операций вставки одного символа, удаления одного символа и замены одного символа на другой, необходимых для превращения одной строки в другую.}}&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
&lt;br /&gt;
Для расстояния Левенштейна справедливы следующие утверждения:&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \ge | |S_1| - |S_2| |&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \le max( |S_1| , |S_2| )&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) = 0 \Leftrightarrow S_1 = S_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
где &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2)&amp;lt;/tex&amp;gt; — расстояние Левенштейна между строками &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, а |S| - длина строки S.&lt;br /&gt;
&lt;br /&gt;
== Разные цены операций ==&lt;br /&gt;
&lt;br /&gt;
Цены операций могут зависеть от вида операции (вставка, удаление, замена) и/или от участвующих в ней символов, отражая разную вероятность разных ошибок при вводе текста, и т. п. В общем случае:&lt;br /&gt;
* w(a, b) — цена замены символа a на символ b&lt;br /&gt;
* w(ε, b) — цена вставки символа b&lt;br /&gt;
* w(a, ε) — цена удаления символа a&lt;br /&gt;
&lt;br /&gt;
Для решения задачи о редакционном расстоянии, необходимо найти последовательность замен, минимизирующую суммарную цену. Расстояние Левенштейна является частным случаем этой задачи при&lt;br /&gt;
* w(a, а) = 0&lt;br /&gt;
* w(a, b) = 1 при a≠b&lt;br /&gt;
* w(ε, b) = 1&lt;br /&gt;
* w(a, ε) = 1&lt;br /&gt;
&lt;br /&gt;
Как частный случай, так и задачу для произвольных w, решает алгоритм Вагнера — Фишера, приведённый ниже. Здесь и ниже мы считаем, что все w неотрицательны, и действует правило треугольника: если две последовательные операции можно заменить одной, это не ухудшает общую цену (например, заменить символ x на y, а потом с y на z не лучше, чем сразу x на z).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Формула ==&lt;br /&gt;
&lt;br /&gt;
Будем считать, что элементы строк нумеруются с первого, как принято в математике, а не нулевого.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; — две строки (длиной &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; соответственно) над некоторым алфавитом, тогда редакционное расстояние &amp;lt;tex&amp;gt;\rm{d}(S_1, S_2)&amp;lt;/tex&amp;gt; можно подсчитать по следующей рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\ \rm{d}(S_1, S_2) = \rm{D}(M,N)&amp;lt;/tex&amp;gt; , где&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\rm{D}(i, j) = \left\{\begin{array}{llcl}&lt;br /&gt;
0&amp;amp;&amp;amp;;&amp;amp;i = 0,\ j = 0\\&lt;br /&gt;
i&amp;amp;&amp;amp;;&amp;amp;j = 0,\ i &amp;gt; 0\\&lt;br /&gt;
j&amp;amp;&amp;amp;;&amp;amp;i = 0,\ j &amp;gt; 0\\&lt;br /&gt;
D(i - 1, j - 1)&amp;amp;&amp;amp;;&amp;amp;S_1[i] = S_2[j]\\&lt;br /&gt;
\rm{min}(\\&lt;br /&gt;
&amp;amp;\rm{D}(i, j - 1) + insertCost\\&lt;br /&gt;
&amp;amp;\rm{D}(i - 1, j) + deleteCost&amp;amp;;&amp;amp;j &amp;gt; 0,\ i &amp;gt; 0,\ S_1[i] \ne S_2[j]\\&lt;br /&gt;
&amp;amp;\rm{D}(i - 1, j - 1) + replaceCost\\&lt;br /&gt;
)&lt;br /&gt;
\end{array}\right.&lt;br /&gt;
&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\min(a, b, c)&amp;lt;/tex&amp;gt; возвращает наименьший из аргументов.&lt;br /&gt;
&lt;br /&gt;
=== Доказательство ===&lt;br /&gt;
&lt;br /&gt;
Рассмотрим формулу более подробно. Здесь &amp;lt;tex&amp;gt;D(i, j)&amp;lt;/tex&amp;gt; — расстояние между префиксами строк: первыми i символами строки &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и первыми j символами строки &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;. Очевидно, что редакционное расстояние между двумя пустыми строками равно нулю. Так же очевидно то, что чтобы получить пустую строку из строки длиной &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, нужно совершить &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; операций удаления, а чтобы получить строку длиной &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; из пустой, нужно произвести &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; операций вставки. Осталось рассмотреть нетривиальный случай, когда обе строки непусты.&lt;br /&gt;
&lt;br /&gt;
Для начала заметим, что в оптимальной последовательности операций, их можно произвольно менять местами. В самом деле, рассмотрим две последовательные операции:&lt;br /&gt;
* Две замены одного и того же символа — неоптимально (если мы заменили x на y, потом y на z, выгоднее было сразу заменить x на z).&lt;br /&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;
&lt;br /&gt;
Пускай &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; кончается на символ «a», &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; кончается на символ «b». Есть три варианта:&lt;br /&gt;
# Символ «а», на который кончается &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, в какой-то момент был стёрт. Сделаем это стирание первой операцией. Тогда мы стёрли символ «a», после чего превратили первые &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; (на что потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)&amp;lt;/tex&amp;gt; операций), значит, всего потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)+1&amp;lt;/tex&amp;gt; операций&lt;br /&gt;
# Символ «b», на который кончается &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, в какой-то момент был добавлен. Сделаем это добавление последней операцией. Мы превратили &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в первые &amp;lt;tex&amp;gt;j-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, после чего добавили «b». Аналогично предыдущему случаю, потребовалось &amp;lt;tex&amp;gt;D(i,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
# Оба предыдущих утверждения неверны. Если мы добавляли символы справа от финального «a», то чтобы сделать последним символом «b», мы должны были или в какой-то момент добавить его (но тогда утверждение 2 было бы верно), либо заменить на него один из этих добавленных символов (что тоже невозможно, потому что добавление символа с его последующей заменой неоптимально). Значит, символов справа от финального «a» мы не добавляли. Самого финального «a» мы не стирали, поскольку утверждение 1 неверно. Значит, единственный способ изменения последнего символа — его замена. Заменять его 2 или больше раз неоптимально. Значит,&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a=b&amp;lt;/tex&amp;gt;, мы последний символ не меняли. Поскольку мы его также не стирали и не приписывали ничего справа от него, он не влиял на наши действия, и, значит, мы выполнили &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a\ne b&amp;lt;/tex&amp;gt;, мы последний символ меняли один раз. Сделаем эту замену первой. В дальнейшем, аналогично предыдущему случаю, мы должны выполнить &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций, значит, всего потребуется &amp;lt;tex&amp;gt;D(i-1,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Вагнера — Фишера ==&lt;br /&gt;
&lt;br /&gt;
Для нахождения кратчайшего расстояния необходимо вычислить матрицу D, используя [[#Формула|вышеприведённую формулу]]. Её можно вычислять как по строкам, так и по столбцам. &lt;br /&gt;
Псевдокод алгоритма, написанный при произвольных ценах замен, вставок и удалений (важно помнить, что элементы нумеруются с 1):&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 D(0,0) = 0&lt;br /&gt;
 для всех j от 1 до N&lt;br /&gt;
   D(0,j) = D(0,j-1) + цена вставки символа S2[j]&lt;br /&gt;
 для всех i от 1 до M&lt;br /&gt;
   D(i,0) = D(i-1,0) + цена удаления символа S1[i]&lt;br /&gt;
   для всех j от 1 до N&lt;br /&gt;
     D(i,j) = min(&lt;br /&gt;
        D(i-1, j) + цена удаления символа S1[i],&lt;br /&gt;
        D(i, j-1) + цена вставки символа S2[j],&lt;br /&gt;
        D(i-1, j-1) + цена замены символа S1[i] на символ S2[j]&lt;br /&gt;
     )&lt;br /&gt;
 вернуть D(M,N)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Память ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм в виде, описанном выше, требует &amp;lt;tex&amp;gt;\Theta(M \cdot N)&amp;lt;/tex&amp;gt; операций и такую же память, однако, если требуется только расстояние, легко уменьшить требуемую память до &amp;lt;tex&amp;gt;\Theta(\min(M,N))&amp;lt;/tex&amp;gt;. Для этого надо учесть, что после вычисления любой строки предыдущая строка больше не нужна. Более того, после вычисления D(i, j) не нужны также D(i-1,0) … D(i-1,j-1). Поэтому алгоритм можно переписать как&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 для всех i от 0 до M&lt;br /&gt;
   для всех j от 0 до N&lt;br /&gt;
     вычислить D(i, j)&lt;br /&gt;
     если i&amp;gt;0 и j&amp;gt;0&lt;br /&gt;
       стереть D(i-1, j-1)&lt;br /&gt;
 вернуть D(M, N)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Рекурсивный алгоритм ==&lt;br /&gt;
Для того, чтобы обеспечить время &amp;lt;math&amp;gt;\Theta(M \cdot N)&amp;lt;/math&amp;gt; при памяти &amp;lt;math&amp;gt;\Theta(\min(M,N))&amp;lt;/math&amp;gt;, определим матрицу E минимальных расстояний между ''суффиксами'' строк, то есть E(i, j) — расстояние между последними i символами &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; и последними j символами &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;. Очевидно, матрицу E можно вычислить аналогично матрице D, и так же быстро.&lt;br /&gt;
&lt;br /&gt;
Теперь опишем алгоритм, считая, что &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; — кратчайшая из двух строк.&lt;br /&gt;
&lt;br /&gt;
* Если длина одной из строк (или обеих) не больше 1, задача тривиальна. Если нет, выполним следующие шаги.&lt;br /&gt;
* Разделим строку &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; на две подстроки длиной &amp;lt;math&amp;gt;M/2&amp;lt;/math&amp;gt;. (Если M нечётно, то длины подстрок будут &amp;lt;math&amp;gt;(M-1)/2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(M+1)/2&amp;lt;/math&amp;gt;.) Обозначим подстроки &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Для вычислим последнюю строку матрицы D для строк &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;, последнюю строку матрицы E для строк &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Найдём i такое, что &amp;lt;math&amp;gt;D(|S_1^-|, i) + E(|S_1^+|, N-i)&amp;lt;/math&amp;gt; минимально. Здесь D и Е — матрицы из предыдущего шага, но мы используем только их последние строки. Таким образом, мы нашли разбиение &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; на две подстроки, минимизирующее сумму расстояния левой половины &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; до левой части &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; и расстояния правой половины &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; до правой части &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;. Следовательно, левая подстрока &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; соответствует левой половине &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;, а правая — правой.&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; в левую часть &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; (то есть в подстроку &amp;lt;math&amp;gt;S_2[1...i]&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; в правую часть &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; (то есть в подстроку &amp;lt;math&amp;gt;S_2[i+1...N]&amp;lt;/math&amp;gt;).&lt;br /&gt;
* Объединяем оба редакционных предписания.&lt;br /&gt;
&lt;br /&gt;
В псевдокоде:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 '''String''' '''levensteinInstruction'''('''String''' s1, '''String''' s2){&lt;br /&gt;
    '''if''' ( s1.length &amp;lt;= 1 || s2.length &amp;lt;= 1 )&lt;br /&gt;
        Решаем тривиально, возвращаем редакционное предписание&lt;br /&gt;
        &lt;br /&gt;
    //Иначе:&lt;br /&gt;
    '''String''' s1l, s1r, s2l, s2r&lt;br /&gt;
    '''if''' ( s2.length &amp;lt; s1.length )&lt;br /&gt;
        s1l = s1.substring(0, s1.length / 2)   //&amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt;&lt;br /&gt;
        s1r = s1.substring(s1.length / 2, s1.length)   //&amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' [] d = '''calcD'''(s1l, s2)   //Вычисляем последнюю строку матрицы D для &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' [] e = '''calcE'''(s1r, s2)   //Вычисляем последнюю строку матрицы E для &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' k = 0&lt;br /&gt;
        '''for''' ('''int''' i = 1; i &amp;lt;= s2.length; i++)&lt;br /&gt;
            '''if''' (d[i] + e[s2.length - i] &amp;lt; d[k] + e[s2.length - k])&lt;br /&gt;
                k = i&lt;br /&gt;
        s2l = s2.substring(0, k)&lt;br /&gt;
        s2r = s2.substring(k, s2.length)&lt;br /&gt;
    '''else'''&lt;br /&gt;
        //s1 - меньшая строка&lt;br /&gt;
        s2l = s2.substring(0, s2.length / 2)   //&amp;lt;math&amp;gt;S_2^-&amp;lt;/math&amp;gt;&lt;br /&gt;
        s2r = s2.substring(s2.length / 2, s2.length)   //&amp;lt;math&amp;gt;S_2^+&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' [] d = '''calcD'''(s2l, s1)   //Вычисляем последнюю строку матрицы D для &amp;lt;math&amp;gt;S_2^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' [] e = '''calcE'''(s2r, s1)   //Вычисляем последнюю строку матрицы E для &amp;lt;math&amp;gt;S_2^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;&lt;br /&gt;
        '''int''' k = 0&lt;br /&gt;
        '''for''' ('''int''' i = 1; i &amp;lt;= s1.length; i++)&lt;br /&gt;
            '''if''' (d[i] + e[s1.length - i] &amp;lt; d[k] + e[s1.length - k])&lt;br /&gt;
                k = i&lt;br /&gt;
        s1l = s1.substring(0, k)&lt;br /&gt;
        s1r = s1.substring(k, s1.length)&lt;br /&gt;
    '''return''' '''levensteinInstruction'''(s1l, s2l) + '''levensteinInstruction'''(s1r, s2r)&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Время выполнения удовлетворяет (с точностью до умножения на константу) условию&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N'),\ 0\le N'\le N&amp;lt;/math&amp;gt;,&lt;br /&gt;
Докажем:&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N) \le 2MN&amp;lt;/math&amp;gt;&lt;br /&gt;
База индукции очевидна&lt;br /&gt;
: &amp;lt;math&amp;gt;T(1,N) = N \le 2N&amp;lt;/math&amp;gt;&lt;br /&gt;
Пусть для всех &amp;lt;math&amp;gt;M' &amp;lt; M&amp;lt;/math&amp;gt; выполнено &amp;lt;math&amp;gt;T(M',N') \le 2M'N'&amp;lt;/math&amp;gt;.  &lt;br /&gt;
Тогда учитывая &amp;lt;math&amp;gt;T(M/2,N') \le 2(M/2)N'&amp;lt;/math&amp;gt;,  &amp;lt;math&amp;gt;T(M/2,N-N') \le 2(M/2)(N-N')&amp;lt;/math&amp;gt;, получим:&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N') \le&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; MN+2(M/2)N'+2(M/2)(N-N')=2MN&amp;lt;/math&amp;gt;&lt;br /&gt;
следовательно&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N) = \Theta(M \cdot N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для вычисления последних строк матриц D, E можно использовать два глобальных двумерных массива размера &amp;lt;math&amp;gt;2 \times (min(M, N)+1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Т.к. мы вычисляем функцию рекурсивно, требуемый размер стека тоже следует учесть. На стек вызовов потребуется &amp;lt;math&amp;gt;\Theta(log(max(M,N))&amp;lt;/math&amp;gt; памяти, потому общая оценка использования памяти будет &amp;lt;math&amp;gt; \Theta(min(M,N)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Редакционное предписание ==&lt;br /&gt;
&lt;br /&gt;
''Редакционным предписанием'' называется последовательность действий, необходимых для получения из первой строки второй кратчайшим образом. Обычно действия обозначаются так: '''D''' (англ. delete) — удалить, '''I''' (англ. insert) — вставить, '''R''' (англ. replace) — заменить, '''M''' (англ. match) — совпадение.&lt;br /&gt;
&lt;br /&gt;
Например, для 2-х строк «hell123» и «hello214» можно построить следующую таблицу преобразований:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!'''M''' ||'''M''' ||'''M''' ||'''M''' ||'''R''' ||'''M''' ||'''R''' ||'''I'''&lt;br /&gt;
|-&lt;br /&gt;
|h ||e ||l ||l ||1 ||2||3 ||&lt;br /&gt;
|-&lt;br /&gt;
|h ||e||l ||l ||o ||2 ||1 ||4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Levenshtein_distance Wikipedia - Levenshtein distance]&lt;br /&gt;
&lt;br /&gt;
[http://pastie.org/5574488 Реализация рекурсивного алгоритма поиска редакционного предписания на языке Java]&lt;br /&gt;
&lt;br /&gt;
Романовский И.В. &amp;quot;Дискретный анализ&amp;quot;. Третье издание. Стр. 103 - 105&lt;br /&gt;
&lt;br /&gt;
[[Категория:Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория:Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28461</id>
		<title>Независимые случайные величины</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28461"/>
				<updated>2012-12-26T18:04:34Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: Оформление дробей&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Определения ==&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def1&lt;br /&gt;
|definition=Cлучайные величины &amp;lt;math&amp;gt; \xi&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\eta&amp;lt;/math&amp;gt; называются '''независимыми''', если &amp;lt;math&amp;gt;\forall \alpha ,\beta \in \mathbb R&amp;lt;/math&amp;gt; события &amp;lt;math&amp;gt;[ \xi \leqslant \alpha ]&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;[ \eta \leqslant \beta ]&amp;lt;/math&amp;gt; независимы.&amp;lt;br&amp;gt; &amp;lt;math&amp;gt;P((\xi \leqslant \alpha) \cap (\eta \leqslant \beta)) = P(\xi \leqslant \alpha)·P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Иначе говоря, две случайные величины называются независимыми, если по значению одной нельзя сделать выводы о значении другой.&lt;br /&gt;
&lt;br /&gt;
=== Независимость в совокупности ===&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def2&lt;br /&gt;
|definition=Случайные величины &amp;lt;math&amp;gt;\xi_1,...,\xi_n&amp;lt;/math&amp;gt; называются '''независимы в совокупности''', если события &amp;lt;math&amp;gt;\xi_1 \leqslant \alpha_1,...,\xi_n \leqslant \alpha_n&amp;lt;/math&amp;gt; независимы в совокупности&amp;lt;ref&amp;gt;[[Независимые события]]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
== Примеры ==&lt;br /&gt;
&lt;br /&gt;
==== Карты ====&lt;br /&gt;
&lt;br /&gt;
Пусть есть колода из 36 карт (4 масти и 9 номиналов). Мы вытягиваем одну карту из случайным образом перемешанной колоды (вероятности вытягивания каждой отдельной карты равны). Определим следующие случайные величины:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\xi&amp;lt;/math&amp;gt; - масть вытянутой карты : 0 - червы, 1 - пики, 2 - крести, 3 - бубны&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\eta&amp;lt;/math&amp;gt; - номинал вытянутой карты : 0 - номиналы 6 7 8 9 10; 1 - валет, дама, король, туз&lt;br /&gt;
&lt;br /&gt;
Для доказательства того, что &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; независимы, требуется рассмотреть все &amp;lt;math&amp;gt;\alpha,\beta&amp;lt;/math&amp;gt; и проверить выполнение равенства:&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) = P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для примера рассмотрим &amp;lt;math&amp;gt;\alpha = 0, \beta = 0&amp;lt;/math&amp;gt;, остальные рассматриваются аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 0)) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{5}{36} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P(\xi \leqslant 0) \cdot P(\eta \leqslant 0) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{4} &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; \cdot &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{5}{9} &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{5}{36} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Тетраэдр ====&lt;br /&gt;
Рассмотрим вероятностное пространство «тетраэдр». Каждое число соответствует грани тетраэдра (по аналогии с игральной костью): &amp;lt;math&amp;gt;\Omega = \mathcal {f} 0, 1, 2, 3 \mathcal {g}&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;\xi (i) = i~mod~2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\eta(i) = \left \lfloor i / 2 \right \rfloor&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим случай: &amp;lt;math&amp;gt;\alpha = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\beta = 1&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;P(\xi \leqslant 0) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{2} &amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P(\eta \leqslant 1) = 1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{2} &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для этих значений &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\beta&amp;lt;/math&amp;gt; события являются независимыми, так же, как и для других (рассматривается аналогично), поэтому эти случайные величины независимы.&lt;br /&gt;
&lt;br /&gt;
Заметим, что если: &amp;lt;math&amp;gt;\xi (i) = i~mod~3&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\eta(i) = \left \lfloor i / 3 \right \rfloor&amp;lt;/math&amp;gt;, то эти величины зависимы: положим &amp;lt;math&amp;gt;\alpha = 0, \beta = 0&amp;lt;/math&amp;gt;. Тогда &amp;lt;math&amp;gt;P(\xi \leqslant 0) =  &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{2} &amp;lt;/math&amp;gt; , &amp;lt;math&amp;gt;P(\eta \leqslant 0) =  &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{3}{4} &amp;lt;/math&amp;gt; , &amp;lt;math&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) =  &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{4} &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;  \neq P(\xi \leqslant 0) P(\eta \leqslant 0)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Честная игральная кость ====&lt;br /&gt;
Рассмотрим вероятностное пространство «честная игральная кость»: &amp;lt;math&amp;gt;\Omega = \mathcal {f} 1, 2, 3, 4, 5, 6 \mathcal {g}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\xi (i) = i~mod~2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\eta (i) = \mathcal {b} i / 3 \mathcal {c}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Для того, чтобы показать, что величины &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; зависимы, надо найти такие &amp;lt;math&amp;gt;\alpha, \beta&amp;lt;/math&amp;gt;, при которых&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) \neq P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;При \alpha = 0, \beta = 1&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{2}{6} &amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{3} &amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P(\xi \leqslant 0) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{2} &amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P(\eta \leqslant 1) = &amp;lt;/math&amp;gt; &amp;lt;math dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{5}{6} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) \neq P(\xi \leqslant 0) \cdot P(\eta \leqslant 1)&amp;lt;/math&amp;gt;, откуда видно, что величины не являются независимыми.&lt;br /&gt;
&lt;br /&gt;
== Примечания ==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://nsu.ru/mmf/tvims/chernova/tv/lec/node38.html Независимость случайных величин]&lt;br /&gt;
&lt;br /&gt;
[http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C_(%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B2%D0%B5%D1%80%D0%BE%D1%8F%D1%82%D0%BD%D0%BE%D1%81%D1%82%D0%B5%D0%B9)#.D0.9D.D0.B5.D0.B7.D0.B0.D0.B2.D0.B8.D1.81.D0.B8.D0.BC.D1.8B.D0.B5_.D1.81.D0.BB.D1.83.D1.87.D0.B0.D0.B9.D0.BD.D1.8B.D0.B5_.D0.B2.D0.B5.D0.BB.D0.B8.D1.87.D0.B8.D0.BD.D1.8B Независимость (теория вероятностей) {{---}} Википедия]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Теория вероятности]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28457</id>
		<title>Независимые случайные величины</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28457"/>
				<updated>2012-12-26T17:36:24Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: Оформление формул&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Определения ==&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def1&lt;br /&gt;
|definition=Cлучайные величины &amp;lt;tex&amp;gt; \xi&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\eta&amp;lt;/tex&amp;gt; называются '''независимыми''', если &amp;lt;tex&amp;gt;\forall \alpha ,\beta \in \mathbb R&amp;lt;/tex&amp;gt; события &amp;lt;tex&amp;gt;[ \xi \leqslant \alpha ]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;[ \eta \leqslant \beta ]&amp;lt;/tex&amp;gt; независимы.&amp;lt;br&amp;gt; &amp;lt;tex&amp;gt;P((\xi \leqslant \alpha) \cap (\eta \leqslant \beta)) = P(\xi \leqslant \alpha)·P(\eta \leqslant \beta)&amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Иначе говоря, две случайные величины называются независимыми, если по значению одной нельзя сделать выводы о значении другой.&lt;br /&gt;
&lt;br /&gt;
=== Независимость в совокупности ===&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def2&lt;br /&gt;
|definition=Случайные величины &amp;lt;tex&amp;gt;\xi_1,...,\xi_n&amp;lt;/tex&amp;gt; называются '''независимы в совокупности''', если события &amp;lt;tex&amp;gt;\xi_1 \leqslant \alpha_1,...,\xi_n \leqslant \alpha_n&amp;lt;/tex&amp;gt; независимы в совокупности&amp;lt;ref&amp;gt;[[Независимые события]]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
== Примеры ==&lt;br /&gt;
&lt;br /&gt;
==== Карты ====&lt;br /&gt;
&lt;br /&gt;
Пусть есть колода из 36 карт (4 масти и 9 номиналов). Мы вытягиваем одну карту из случайным образом перемешанной колоды (вероятности вытягивания каждой отдельной карты равны). Определим следующие случайные величины:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\xi&amp;lt;/math&amp;gt; - масть вытянутой карты : 0 - червы, 1 - пики, 2 - крести, 3 - бубны&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\eta&amp;lt;/math&amp;gt; - номинал вытянутой карты : 0 - номиналы 6 7 8 9 10; 1 - валет, дама, король, туз&lt;br /&gt;
&lt;br /&gt;
Для доказательства того, что &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; независимы, требуется рассмотреть все &amp;lt;math&amp;gt;\alpha,\beta&amp;lt;/math&amp;gt; и проверить выполнение равенства:&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) = P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для примера рассмотрим &amp;lt;math&amp;gt;\alpha = 0, \beta = 0&amp;lt;/math&amp;gt;, остальные рассматриваются аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math dpi = &amp;quot;160&amp;quot;&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 0)) = \frac{5}{36}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math dpi = &amp;quot;160&amp;quot;&amp;gt;P(\xi \leqslant 0) \cdot P(\eta \leqslant 0) = \frac{1}{4} \cdot \frac{5}{9} = \frac{5}{36}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Тетраэдр ====&lt;br /&gt;
Рассмотрим вероятностное пространство «тетраэдр». Каждое число соответствует грани тетраэдра (по аналогии с игральной костью): &amp;lt;tex&amp;gt;\Omega = \mathcal {f} 0, 1, 2, 3 \mathcal {g}&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;\xi (i) = i~mod~2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta(i) = \left \lfloor i / 2 \right \rfloor&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим случай: &amp;lt;tex&amp;gt;\alpha = 0&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\beta = 1&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;P(\xi \leqslant 0) = 1/2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P(\eta \leqslant 1) = 1&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) = 1/2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для этих значений &amp;lt;tex&amp;gt;\alpha&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\beta&amp;lt;/tex&amp;gt; события являются независимыми, так же, как и для других (рассматривается аналогично), поэтому эти случайные величины независимы.&lt;br /&gt;
&lt;br /&gt;
Заметим, что если: &amp;lt;tex&amp;gt;\xi (i) = i~mod~3&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta(i) = \left \lfloor i / 3 \right \rfloor&amp;lt;/tex&amp;gt;, то эти величины зависимы: положим &amp;lt;tex&amp;gt;\alpha = 0, \beta = 0&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;P(\xi \leqslant 0) = 1/2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P(\eta \leqslant 0) = 3/4&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) = 1/4 \neq P(\xi \leqslant 0) P(\eta \leqslant 0)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Честная игральная кость ====&lt;br /&gt;
Рассмотрим вероятностное пространство «честная игральная кость»: &amp;lt;tex&amp;gt;\Omega = \mathcal {f} 1, 2, 3, 4, 5, 6 \mathcal {g}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\xi (i) = i~mod~2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta (i) = \mathcal {b} i / 3 \mathcal {c}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Для того, чтобы показать, что величины &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; зависимы, надо найти такие &amp;lt;math&amp;gt;\alpha, \beta&amp;lt;/math&amp;gt;, при которых&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) \neq P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;При \alpha = 0, \beta = 1&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math dpi = &amp;quot;160&amp;quot;&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) = \frac{2}{6} = \frac{1}{3}&amp;lt;/math&amp;gt;, &amp;lt;math dpi = &amp;quot;160&amp;quot;&amp;gt;P(\xi \leqslant 0) = \frac{1}{2}&amp;lt;/math&amp;gt;, &amp;lt;math dpi = &amp;quot;160&amp;quot;&amp;gt;P(\eta \leqslant 1) = \frac{5}{6}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) \neq P(\xi \leqslant 0) \cdot P(\eta \leqslant 1)&amp;lt;/math&amp;gt;, откуда видно, что величины не являются независимыми.&lt;br /&gt;
&lt;br /&gt;
== Примечания ==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://nsu.ru/mmf/tvims/chernova/tv/lec/node38.html Независимость случайных величин]&lt;br /&gt;
&lt;br /&gt;
[http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C_(%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B2%D0%B5%D1%80%D0%BE%D1%8F%D1%82%D0%BD%D0%BE%D1%81%D1%82%D0%B5%D0%B9)#.D0.9D.D0.B5.D0.B7.D0.B0.D0.B2.D0.B8.D1.81.D0.B8.D0.BC.D1.8B.D0.B5_.D1.81.D0.BB.D1.83.D1.87.D0.B0.D0.B9.D0.BD.D1.8B.D0.B5_.D0.B2.D0.B5.D0.BB.D0.B8.D1.87.D0.B8.D0.BD.D1.8B Независимость (теория вероятностей) {{---}} Википедия]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Теория вероятности]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28455</id>
		<title>Независимые случайные величины</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D1%8B%D0%B5_%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B5_%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%BD%D1%8B&amp;diff=28455"/>
				<updated>2012-12-26T16:54:58Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: Скорректировал пример с игральной костью, добавил пример с картами&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Определения ==&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def1&lt;br /&gt;
|definition=Cлучайные величины &amp;lt;tex&amp;gt; \xi&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\eta&amp;lt;/tex&amp;gt; называются '''независимыми''', если &amp;lt;tex&amp;gt;\forall \alpha ,\beta \in \mathbb R&amp;lt;/tex&amp;gt; события &amp;lt;tex&amp;gt;[ \xi \leqslant \alpha ]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;[ \eta \leqslant \beta ]&amp;lt;/tex&amp;gt; независимы.&amp;lt;br&amp;gt; &amp;lt;tex&amp;gt;P((\xi \leqslant \alpha) \cap (\eta \leqslant \beta)) = P(\xi \leqslant \alpha)·P(\eta \leqslant \beta)&amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Иначе говоря, две случайные величины называются независимыми, если по значению одной нельзя сделать выводы о значении другой.&lt;br /&gt;
&lt;br /&gt;
=== Независимость в совокупности ===&lt;br /&gt;
{{Определение&lt;br /&gt;
|id=def2&lt;br /&gt;
|definition=Случайные величины &amp;lt;tex&amp;gt;\xi_1,...,\xi_n&amp;lt;/tex&amp;gt; называются '''независимы в совокупности''', если события &amp;lt;tex&amp;gt;\xi_1 \leqslant \alpha_1,...,\xi_n \leqslant \alpha_n&amp;lt;/tex&amp;gt; независимы в совокупности&amp;lt;ref&amp;gt;[[Независимые события]]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
== Примеры ==&lt;br /&gt;
&lt;br /&gt;
==== Карты ====&lt;br /&gt;
&lt;br /&gt;
Пусть есть колода из 36 карт (4 масти и 9 номиналов). Мы вытягиваем одну карту из случайным образом перемешанной колоды (вероятности вытягивания каждой отдельной карты равны). Определим следующие случайные величины:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\xi&amp;lt;/math&amp;gt; - масть вытянутой карты : 0 - червы, 1 - пики, 2 - крести, 3 - бубны&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\eta&amp;lt;/math&amp;gt; - номинал вытянутой карты : 0 - номиналы 6 7 8 9 10; 1 - валет, дама, король, туз&lt;br /&gt;
&lt;br /&gt;
Для доказательства того, что &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; независимы, требуется рассмотреть все &amp;lt;math&amp;gt;\alpha,\beta&amp;lt;/math&amp;gt; и проверить выполнение равенства:&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) = P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для примера рассмотрим &amp;lt;math&amp;gt;\alpha = 0, \beta = 0&amp;lt;/math&amp;gt;, остальные рассматриваются аналогично:&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 0)) = \frac{5}{36}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P(\xi \leqslant 0) \cdot P(\eta \leqslant 0) = \frac{1}{4} \cdot \frac{5}{9} = \frac{5}{36}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Тетраэдр ====&lt;br /&gt;
Рассмотрим вероятностное пространство «тетраэдр». Каждое число соответствует грани тетраэдра (по аналогии с игральной костью): &amp;lt;tex&amp;gt;\Omega = \mathcal {f} 0, 1, 2, 3 \mathcal {g}&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;\xi (i) = i~mod~2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta(i) = \left \lfloor i / 2 \right \rfloor&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим случай: &amp;lt;tex&amp;gt;\alpha = 0&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\beta = 1&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;P(\xi \leqslant 0) = 1/2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P(\eta \leqslant 1) = 1&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) = 1/2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для этих значений &amp;lt;tex&amp;gt;\alpha&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\beta&amp;lt;/tex&amp;gt; события являются независимыми, так же, как и для других (рассматривается аналогично), поэтому эти случайные величины независимы.&lt;br /&gt;
&lt;br /&gt;
Заметим, что если: &amp;lt;tex&amp;gt;\xi (i) = i~mod~3&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta(i) = \left \lfloor i / 3 \right \rfloor&amp;lt;/tex&amp;gt;, то эти величины зависимы: положим &amp;lt;tex&amp;gt;\alpha = 0, \beta = 0&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;P(\xi \leqslant 0) = 1/2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P(\eta \leqslant 0) = 3/4&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;P((\xi \leqslant 0) \cap (\eta \leqslant 1)) = 1/4 \neq P(\xi \leqslant 0) P(\eta \leqslant 0)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Честная игральная кость ====&lt;br /&gt;
Рассмотрим вероятностное пространство «честная игральная кость»: &amp;lt;tex&amp;gt;\Omega = \mathcal {f} 1, 2, 3, 4, 5, 6 \mathcal {g}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\xi (i) = i~mod~2&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\eta (i) = \mathcal {b} i / 3 \mathcal {c}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Для того, чтобы показать, что величины &amp;lt;math&amp;gt;\xi, \eta&amp;lt;/math&amp;gt; зависимы, надо найти такие &amp;lt;math&amp;gt;\alpha, \beta&amp;lt;/math&amp;gt;, при которых&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant \alpha)\cap(\eta \leqslant \beta)) \neq P(\xi \leqslant \alpha) \cdot P(\eta \leqslant \beta)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\alpha = 0, \beta = 1&amp;lt;/math&amp;gt;, тогда &amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) = \frac{2}{6} = \frac{1}{3}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P(\xi \leqslant 0) = \frac{1}{2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;P(\eta \leqslant 1) = \frac{5}{6}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;P((\xi \leqslant 0)\cap(\eta \leqslant 1)) \neq P(\xi \leqslant 0) \cdot P(\eta \leqslant 1)&amp;lt;/math&amp;gt;, откуда видно, что величины не являются независимыми.&lt;br /&gt;
&lt;br /&gt;
== Примечания ==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://nsu.ru/mmf/tvims/chernova/tv/lec/node38.html Независимость случайных величин]&lt;br /&gt;
&lt;br /&gt;
[http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C_(%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B2%D0%B5%D1%80%D0%BE%D1%8F%D1%82%D0%BD%D0%BE%D1%81%D1%82%D0%B5%D0%B9)#.D0.9D.D0.B5.D0.B7.D0.B0.D0.B2.D0.B8.D1.81.D0.B8.D0.BC.D1.8B.D0.B5_.D1.81.D0.BB.D1.83.D1.87.D0.B0.D0.B9.D0.BD.D1.8B.D0.B5_.D0.B2.D0.B5.D0.BB.D0.B8.D1.87.D0.B8.D0.BD.D1.8B Независимость (теория вероятностей) {{---}} Википедия]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Теория вероятности]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=28453</id>
		<title>Задача о редакционном расстоянии, алгоритм Вагнера-Фишера</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=28453"/>
				<updated>2012-12-26T16:44:26Z</updated>
		
		<summary type="html">&lt;p&gt;Georgeee: Добавил рекурсивный алгоритм поиска редакционного предписания с линейным использованием памяти&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Расстояние Левенштейна''' (также '''редакционное расстояние''' или '''дистанция редактирования''') между двумя строками в теории информации и компьютерной лингвистике — это минимальное количество операций вставки одного символа, удаления одного символа и замены одного символа на другой, необходимых для превращения одной строки в другую.}}&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
&lt;br /&gt;
Для расстояния Левенштейна справедливы следующие утверждения:&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \ge | |S_1| - |S_2| |&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \le max( |S_1| , |S_2| )&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) = 0 \Leftrightarrow S_1 = S_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
где &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2)&amp;lt;/tex&amp;gt; — расстояние Левенштейна между строками &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, а |S| - длина строки S.&lt;br /&gt;
&lt;br /&gt;
== Разные цены операций ==&lt;br /&gt;
&lt;br /&gt;
Цены операций могут зависеть от вида операции (вставка, удаление, замена) и/или от участвующих в ней символов, отражая разную вероятность разных ошибок при вводе текста, и т. п. В общем случае:&lt;br /&gt;
* w(a, b) — цена замены символа a на символ b&lt;br /&gt;
* w(ε, b) — цена вставки символа b&lt;br /&gt;
* w(a, ε) — цена удаления символа a&lt;br /&gt;
&lt;br /&gt;
Для решения задачи о редакционном расстоянии, необходимо найти последовательность замен, минимизирующую суммарную цену. Расстояние Левенштейна является частным случаем этой задачи при&lt;br /&gt;
* w(a, а) = 0&lt;br /&gt;
* w(a, b) = 1 при a≠b&lt;br /&gt;
* w(ε, b) = 1&lt;br /&gt;
* w(a, ε) = 1&lt;br /&gt;
&lt;br /&gt;
Как частный случай, так и задачу для произвольных w, решает алгоритм Вагнера — Фишера, приведённый ниже. Здесь и ниже мы считаем, что все w неотрицательны, и действует правило треугольника: если две последовательные операции можно заменить одной, это не ухудшает общую цену (например, заменить символ x на y, а потом с y на z не лучше, чем сразу x на z).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Формула ==&lt;br /&gt;
&lt;br /&gt;
Будем считать, что элементы строк нумеруются с первого, как принято в математике, а не нулевого.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; — две строки (длиной &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; соответственно) над некоторым алфавитом, тогда редакционное расстояние &amp;lt;tex&amp;gt;\rm{d}(S_1, S_2)&amp;lt;/tex&amp;gt; можно подсчитать по следующей рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\ \rm{d}(S_1, S_2) = \rm{D}(M,N)&amp;lt;/tex&amp;gt; , где&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\rm{D}(i, j) = \left\{\begin{array}{llcl}&lt;br /&gt;
0&amp;amp;&amp;amp;;&amp;amp;i = 0,\ j = 0\\&lt;br /&gt;
i&amp;amp;&amp;amp;;&amp;amp;j = 0,\ i &amp;gt; 0\\&lt;br /&gt;
j&amp;amp;&amp;amp;;&amp;amp;i = 0,\ j &amp;gt; 0\\&lt;br /&gt;
D(i - 1, j - 1)&amp;amp;&amp;amp;;&amp;amp;S_1[i] = S_2[j]\\&lt;br /&gt;
\rm{min}(\\&lt;br /&gt;
&amp;amp;\rm{D}(i, j - 1) + insertCost\\&lt;br /&gt;
&amp;amp;\rm{D}(i - 1, j) + deleteCost&amp;amp;;&amp;amp;j &amp;gt; 0,\ i &amp;gt; 0,\ S_1[i] \ne S_2[j]\\&lt;br /&gt;
&amp;amp;\rm{D}(i - 1, j - 1) + replaceCost\\&lt;br /&gt;
)&lt;br /&gt;
\end{array}\right.&lt;br /&gt;
&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\min(a, b, c)&amp;lt;/tex&amp;gt; возвращает наименьший из аргументов.&lt;br /&gt;
&lt;br /&gt;
=== Доказательство ===&lt;br /&gt;
&lt;br /&gt;
Рассмотрим формулу более подробно. Здесь &amp;lt;tex&amp;gt;D(i, j)&amp;lt;/tex&amp;gt; — расстояние между префиксами строк: первыми i символами строки &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и первыми j символами строки &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;. Очевидно, что редакционное расстояние между двумя пустыми строками равно нулю. Так же очевидно то, что чтобы получить пустую строку из строки длиной &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, нужно совершить &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; операций удаления, а чтобы получить строку длиной &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; из пустой, нужно произвести &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; операций вставки. Осталось рассмотреть нетривиальный случай, когда обе строки непусты.&lt;br /&gt;
&lt;br /&gt;
Для начала заметим, что в оптимальной последовательности операций, их можно произвольно менять местами. В самом деле, рассмотрим две последовательные операции:&lt;br /&gt;
* Две замены одного и того же символа — неоптимально (если мы заменили x на y, потом y на z, выгоднее было сразу заменить x на z).&lt;br /&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;
&lt;br /&gt;
Пускай &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; кончается на символ «a», &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; кончается на символ «b». Есть три варианта:&lt;br /&gt;
# Символ «а», на который кончается &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, в какой-то момент был стёрт. Сделаем это стирание первой операцией. Тогда мы стёрли символ «a», после чего превратили первые &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; (на что потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)&amp;lt;/tex&amp;gt; операций), значит, всего потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)+1&amp;lt;/tex&amp;gt; операций&lt;br /&gt;
# Символ «b», на который кончается &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, в какой-то момент был добавлен. Сделаем это добавление последней операцией. Мы превратили &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в первые &amp;lt;tex&amp;gt;j-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, после чего добавили «b». Аналогично предыдущему случаю, потребовалось &amp;lt;tex&amp;gt;D(i,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
# Оба предыдущих утверждения неверны. Если мы добавляли символы справа от финального «a», то чтобы сделать последним символом «b», мы должны были или в какой-то момент добавить его (но тогда утверждение 2 было бы верно), либо заменить на него один из этих добавленных символов (что тоже невозможно, потому что добавление символа с его последующей заменой неоптимально). Значит, символов справа от финального «a» мы не добавляли. Самого финального «a» мы не стирали, поскольку утверждение 1 неверно. Значит, единственный способ изменения последнего символа — его замена. Заменять его 2 или больше раз неоптимально. Значит,&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a=b&amp;lt;/tex&amp;gt;, мы последний символ не меняли. Поскольку мы его также не стирали и не приписывали ничего справа от него, он не влиял на наши действия, и, значит, мы выполнили &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a\ne b&amp;lt;/tex&amp;gt;, мы последний символ меняли один раз. Сделаем эту замену первой. В дальнейшем, аналогично предыдущему случаю, мы должны выполнить &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций, значит, всего потребуется &amp;lt;tex&amp;gt;D(i-1,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Вагнера — Фишера ==&lt;br /&gt;
&lt;br /&gt;
Для нахождения кратчайшего расстояния необходимо вычислить матрицу D, используя [[#Формула|вышеприведённую формулу]]. Её можно вычислять как по строкам, так и по столбцам. &lt;br /&gt;
Псевдокод алгоритма, написанный при произвольных ценах замен, вставок и удалений (важно помнить, что элементы нумеруются с 1):&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 D(0,0) = 0&lt;br /&gt;
 для всех j от 1 до N&lt;br /&gt;
   D(0,j) = D(0,j-1) + цена вставки символа S2[j]&lt;br /&gt;
 для всех i от 1 до M&lt;br /&gt;
   D(i,0) = D(i-1,0) + цена удаления символа S1[i]&lt;br /&gt;
   для всех j от 1 до N&lt;br /&gt;
     D(i,j) = min(&lt;br /&gt;
        D(i-1, j) + цена удаления символа S1[i],&lt;br /&gt;
        D(i, j-1) + цена вставки символа S2[j],&lt;br /&gt;
        D(i-1, j-1) + цена замены символа S1[i] на символ S2[j]&lt;br /&gt;
     )&lt;br /&gt;
 вернуть D(M,N)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Память ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм в виде, описанном выше, требует &amp;lt;tex&amp;gt;\Theta(M \cdot N)&amp;lt;/tex&amp;gt; операций и такую же память, однако, если требуется только расстояние, легко уменьшить требуемую память до &amp;lt;tex&amp;gt;\Theta(\min(M,N))&amp;lt;/tex&amp;gt;. Для этого надо учесть, что после вычисления любой строки предыдущая строка больше не нужна. Более того, после вычисления D(i, j) не нужны также D(i-1,0) … D(i-1,j-1). Поэтому алгоритм можно переписать как&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 для всех i от 0 до M&lt;br /&gt;
   для всех j от 0 до N&lt;br /&gt;
     вычислить D(i, j)&lt;br /&gt;
     если i&amp;gt;0 и j&amp;gt;0&lt;br /&gt;
       стереть D(i-1, j-1)&lt;br /&gt;
 вернуть D(M, N)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Рекурсивный алгоритм ==&lt;br /&gt;
Для того, чтобы обеспечить время &amp;lt;math&amp;gt;\Theta(M \cdot N)&amp;lt;/math&amp;gt; при памяти &amp;lt;math&amp;gt;\Theta(\min(M,N))&amp;lt;/math&amp;gt;, определим матрицу E минимальных расстояний между ''суффиксами'' строк, то есть E(i, j) — расстояние между последними i символами &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; и последними j символами &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;. Очевидно, матрицу E можно вычислить аналогично матрице D, и так же быстро.&lt;br /&gt;
&lt;br /&gt;
Теперь опишем алгоритм, считая, что &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; — кратчайшая из двух строк.&lt;br /&gt;
&lt;br /&gt;
* Если длина одной из строк (или обеих) не больше 1, задача тривиальна. Если нет, выполним следующие шаги.&lt;br /&gt;
* Разделим строку &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; на две подстроки длиной &amp;lt;math&amp;gt;M/2&amp;lt;/math&amp;gt;. (Если M нечётно, то длины подстрок будут &amp;lt;math&amp;gt;(M-1)/2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(M+1)/2&amp;lt;/math&amp;gt;.) Обозначим подстроки &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Для вычислим последнюю строку матрицы D для строк &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;, последнюю строку матрицы E для строк &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Найдём i такое, что &amp;lt;math&amp;gt;D(|S_1^-|, i) + E(|S_1^+|, N-i)&amp;lt;/math&amp;gt; минимально. Здесь D и Е — матрицы из предыдущего шага, но мы используем только их последние строки. Таким образом, мы нашли разбиение &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; на две подстроки, минимизирующее сумму расстояния левой половины &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; до левой части &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; и расстояния правой половины &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt; до правой части &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;. Следовательно, левая подстрока &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; соответствует левой половине &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;, а правая — правой.&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; в левую часть &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; (то есть в подстроку &amp;lt;math&amp;gt;S_2[1...i]&amp;lt;/math&amp;gt;)&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; в правую часть &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt; (то есть в подстроку &amp;lt;math&amp;gt;S_2[i+1...N]&amp;lt;/math&amp;gt;).&lt;br /&gt;
* Объединяем оба редакционных предписания.&lt;br /&gt;
&lt;br /&gt;
В псевдокоде:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 String levensteinInstruction(String s1, String s2){&lt;br /&gt;
    if ( s1.length() &amp;lt;= 1 || s2.length() &amp;lt;= 1 )&lt;br /&gt;
      {Решаем тривиально, возвращаем редакционное предписание}&lt;br /&gt;
    //Иначе:&lt;br /&gt;
    String s1l,s1r,s2l,s2r;&lt;br /&gt;
    if ( s2.length() &amp;lt; s1.length() ){&lt;br /&gt;
        s1l = s1.substring(0, s1.length() / 2); //&amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt;&lt;br /&gt;
        s1r = s1.substring(s1.length() / 2, s1.length()); //&amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt;&lt;br /&gt;
        int [] d = calcD(s1l, s2); //Вычисляем последнюю строку матрицы D для &amp;lt;math&amp;gt;S_1^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;&lt;br /&gt;
        int [] e = calcE(s1r, s2); //Вычисляем последнюю строку матрицы E для &amp;lt;math&amp;gt;S_1^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_2&amp;lt;/math&amp;gt;&lt;br /&gt;
        int k = 0;&lt;br /&gt;
        for (int i = 1; i &amp;lt;= s2.length(); i++) {&lt;br /&gt;
            if (d[i] + e[s2.length() - i] &amp;lt; d[k] + e[s2.length() - k]) {&lt;br /&gt;
                k = i;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        s2l = s2.substring(0, k);&lt;br /&gt;
        s2r = s2.substring(k, s2.length());&lt;br /&gt;
    } else {&lt;br /&gt;
        //s1 - меньшая строка&lt;br /&gt;
        s2l = s2.substring(0, s2.length() / 2); //&amp;lt;math&amp;gt;S_2^-&amp;lt;/math&amp;gt;&lt;br /&gt;
        s2r = s2.substring(s2.length() / 2, s2.length()); //&amp;lt;math&amp;gt;S_2^+&amp;lt;/math&amp;gt;&lt;br /&gt;
        int [] d = calcD(s2l, s1); //Вычисляем последнюю строку матрицы D для &amp;lt;math&amp;gt;S_2^-&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;&lt;br /&gt;
        int [] e = calcE(s2r, s1); //Вычисляем последнюю строку матрицы E для &amp;lt;math&amp;gt;S_2^+&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;&lt;br /&gt;
        int k = 0;&lt;br /&gt;
        for (int i = 1; i &amp;lt;= s1.length(); i++) {&lt;br /&gt;
            if (d[i] + e[s1.length() - i] &amp;lt; d[k] + e[s1.length() - k]) {&lt;br /&gt;
                k = i;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        s1l = s1.substring(0, k);&lt;br /&gt;
        s1r = s1.substring(k, s1.length());&lt;br /&gt;
    }&lt;br /&gt;
    return levensteinInstruction(s1l, s2l) + levensteinInstruction(s1r, s2r);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Время выполнения удовлетворяет (с точностью до умножения на константу) условию&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N'),\ 0\le N'\le N&amp;lt;/math&amp;gt;,&lt;br /&gt;
Докажем:&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N) \le 2MN&amp;lt;/math&amp;gt;&lt;br /&gt;
База индукции очевидна&lt;br /&gt;
: &amp;lt;math&amp;gt;T(1,N) = N \le 2N&amp;lt;/math&amp;gt;&lt;br /&gt;
Пусть для всех &amp;lt;math&amp;gt;M' &amp;lt; M&amp;lt;/math&amp;gt; выполнено &amp;lt;math&amp;gt;T(M',N') \le 2M'N'&amp;lt;/math&amp;gt;.  &lt;br /&gt;
Тогда учитывая &amp;lt;math&amp;gt;T(M/2,N') \le 2(M/2)N'&amp;lt;/math&amp;gt;,  &amp;lt;math&amp;gt;T(M/2,N-N') \le 2(M/2)(N-N')&amp;lt;/math&amp;gt;, получим:&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N') \le&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; MN+2(M/2)N'+2(M/2)(N-N')=2MN&amp;lt;/math&amp;gt;&lt;br /&gt;
следовательно&lt;br /&gt;
: &amp;lt;math&amp;gt;T(M,N) = \Theta(M \cdot N)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для вычисления последних строк матриц D, E можно использовать два глобальных двумерных массива размера &amp;lt;math&amp;gt;2 \times (min(M, N)+1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Т.к. мы вычисляем функцию рекурсивно, требуемый размер стека тоже следует учесть. На стек вызовов потребуется &amp;lt;math&amp;gt;\Theta(log(max(M,N))&amp;lt;/math&amp;gt; памяти, потому общая оценка использования памяти будет &amp;lt;math&amp;gt; \Theta(min(M,N)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Редакционное предписание ==&lt;br /&gt;
&lt;br /&gt;
''Редакционным предписанием'' называется последовательность действий, необходимых для получения из первой строки второй кратчайшим образом. Обычно действия обозначаются так: '''D''' (англ. delete) — удалить, '''I''' (англ. insert) — вставить, '''R''' (англ. replace) — заменить, '''M''' (англ. match) — совпадение.&lt;br /&gt;
&lt;br /&gt;
Например, для 2-х строк «hell123» и «hello214» можно построить следующую таблицу преобразований:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!'''M''' ||'''M''' ||'''M''' ||'''M''' ||'''R''' ||'''M''' ||'''R''' ||'''I'''&lt;br /&gt;
|-&lt;br /&gt;
|h ||e ||l ||l ||1 ||2||3 ||&lt;br /&gt;
|-&lt;br /&gt;
|h ||e||l ||l ||o ||2 ||1 ||4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Levenshtein_distance Wikipedia - Levenshtein distance]&lt;br /&gt;
&lt;br /&gt;
[http://pastie.org/5574488 Реализация рекурсивного алгоритма поиска редакционного предписания на языке Java]&lt;br /&gt;
&lt;br /&gt;
Романовский И.В. &amp;quot;Дискретный анализ&amp;quot;. Третье издание. Стр. 103 - 105&lt;br /&gt;
&lt;br /&gt;
[[Категория:Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория:Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>Georgeee</name></author>	</entry>

	</feed>