<?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=Glukos</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=Glukos"/>
		<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/Glukos"/>
		<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=30024</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=30024"/>
				<updated>2013-01-15T15:43:19Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;gt;&lt;br /&gt;
В случае с простым алгоритмом при последующем разбиении класса &amp;lt;tex&amp;gt;S&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;|S| \ge |S_i| + 1&amp;lt;/tex&amp;gt;, отсюда получаем время работы &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30023</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=30023"/>
				<updated>2013-01-15T15:43:05Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;gt;&lt;br /&gt;
В случаем с простым алгоритмом при последующем разбиении класса &amp;lt;tex&amp;gt;S&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;|S| \ge |S_i| + 1&amp;lt;/tex&amp;gt;, отсюда получаем время работы &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30022</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=30022"/>
				<updated>2013-01-15T15:42:32Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;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;|S| \ge |S_i| + 1&amp;lt;/tex&amp;gt;, отсюда получаем время работы &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30021</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=30021"/>
				<updated>2013-01-15T15:41:31Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;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;|S| \ge |S_i| + 1&amp;lt;/tex&amp;gt;. Поэтому, его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30020</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=30020"/>
				<updated>2013-01-15T15:40:31Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;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;|S| \ge |S_i| + 1&amp;lt;/tex&amp;gt;. Поэтому, его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30019</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=30019"/>
				<updated>2013-01-15T15:35:48Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритмов */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;gt;&lt;br /&gt;
В случаем с простым алгоритмом при последующем разбиении можно гарантировать лишь &amp;lt;tex&amp;gt;|S| \ge |S_1| + 1&amp;lt;/tex&amp;gt;, поэтому его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30018</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=30018"/>
				<updated>2013-01-15T15:34:10Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритмов==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;gt;&lt;br /&gt;
В случаем с простым алгоритмом при разбиении можно гарантировать лишь &amp;lt;tex&amp;gt;|S| \ge |S_1| + 1&amp;lt;/tex&amp;gt;, поэтому его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30017</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=30017"/>
				<updated>2013-01-15T15:33:29Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы простого и модифицированного алгоритма */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы простого и модифицированного алгоритма==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовася в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&amp;lt;br&amp;gt;&lt;br /&gt;
В случаем с простым алгоритмом при разбиении можно гарантировать лишь &amp;lt;tex&amp;gt;|S| \ge |S_1| + 1&amp;lt;/tex&amp;gt;, поэтому его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30016</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=30016"/>
				<updated>2013-01-15T15:33:09Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритма */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы простого и модифицированного алгоритма==&lt;br /&gt;
Время работы модифицированного алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовася в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&amp;gt;, получаем указанную оценку.&lt;br /&gt;
В случаем с простым алгоритмом при разбиении можно гарантировать лишь &amp;lt;tex&amp;gt;|S| \ge |S_1| + 1&amp;lt;/tex&amp;gt;, поэтому его время работы оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n^2)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30015</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=30015"/>
				<updated>2013-01-15T15:22:37Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы алгоритма */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритма==&lt;br /&gt;
Время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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;(S, a)&amp;lt;/tex&amp;gt; попала в очередь, и класс &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; использовася в качестве сплиттера, то при последующем разбиении этого класса в очередь будет добавлен класс &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, причем &amp;lt;tex&amp;gt;|S| \ge 2|S_1|&amp;lt;/tex&amp;gt;. Каждое состояние изначально принадлежит лишь одному классу в очереди, тогда из вышеуказанного следует, что каждый переход в автомате будет просмотрен не более, чем &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt; раз. Учитывая, что ребер всего &amp;lt;tex&amp;gt;O(|\Sigma| * n)&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30010</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=30010"/>
				<updated>2013-01-15T15:03:06Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Псевдокод */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  else&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;&lt;br /&gt;
        replace (&amp;lt;tex&amp;gt;R, a&amp;lt;/tex&amp;gt;) in &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; with (&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;) and (&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
      else&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        else&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
==Время работы алгоритма==&lt;br /&gt;
Время работы алгоритма равно &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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; |\Sigma| * n &amp;lt;/tex&amp;gt;, участвует не более чем в &amp;lt;tex&amp;gt; \log{n}&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30005</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=30005"/>
				<updated>2013-01-15T14:54:30Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Псевдокод */&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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S, a&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
      &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
      if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
        &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    else&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;W&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;W&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;
          else&lt;br /&gt;
            if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
              else&lt;br /&gt;
                &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
==Время работы алгоритма==&lt;br /&gt;
Время работы алгоритма равно &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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; |\Sigma| * n &amp;lt;/tex&amp;gt;, участвует не более чем в &amp;lt;tex&amp;gt; \log{n}&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</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=30004</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=30004"/>
				<updated>2013-01-15T14:45:42Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &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;S&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 S&amp;lt;/tex&amp;gt; &lt;br /&gt;
# &amp;lt;tex&amp;gt;\forall r \in R_2 \,\,\, \delta(r, a) \notin S&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;Q&amp;lt;/tex&amp;gt; и класс недопускающих состояний &amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Перебираются символы алфавита &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;, все пары &amp;lt;tex&amp;gt;(Q, a)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(Q \setminus F, a)&amp;lt;/tex&amp;gt; помещаются в очередь.&lt;br /&gt;
# Из очереди извлекается пара &amp;lt;tex&amp;gt;(S, a)&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; далее именуется как сплиттер.&lt;br /&gt;
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; переходят в сплиттер, а второй из всех оставшихся. &lt;br /&gt;
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.&lt;br /&gt;
# Пока очередь не пуста, выполняем п.3 – п.5.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  &amp;lt;tex&amp;gt;W \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      for all &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; &lt;br /&gt;
        &amp;lt;tex&amp;gt;R_1 = R \cap \delta^{-1} (S, a) &amp;lt;/tex&amp;gt;&lt;br /&gt;
        &amp;lt;tex&amp;gt;R_2 = R \setminus R_1&amp;lt;/tex&amp;gt;&lt;br /&gt;
        if &amp;lt;tex&amp;gt; |R_1| \ne 0&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt;|R_2| \ne 0&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;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;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
          &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.&lt;br /&gt;
&lt;br /&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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \notin R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \delta(r, a) \in R_2&amp;lt;/tex&amp;gt; or &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \notin R_1&amp;lt;/tex&amp;gt; and &amp;lt;tex&amp;gt; \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 &amp;lt;/tex&amp;gt; or &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;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&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;B&amp;lt;/tex&amp;gt; из текущего разбиения выполняется &lt;br /&gt;
:&amp;lt;tex&amp;gt;\forall r \in B \,\,\, \delta(r, a) \in R &amp;lt;/tex&amp;gt; or &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_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} множество состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt; {{---}} множество терминальных состояний.&lt;br /&gt;
&amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; {{---}} очередь.&lt;br /&gt;
&amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; {{---}} разбиение множества состояний ДКА.&lt;br /&gt;
&amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; {{---}} класс состояний ДКА.&lt;br /&gt;
  if &amp;lt;tex&amp;gt; |F| \le |Q \setminus F|&amp;lt;/tex&amp;gt;&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;F&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    else&lt;br /&gt;
      &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;Q \setminus F&amp;lt;/tex&amp;gt;)&lt;br /&gt;
  &amp;lt;tex&amp;gt;P \leftarrow \{ F, Q \setminus F \}&amp;lt;/tex&amp;gt;&lt;br /&gt;
  while not &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.isEmpty() &lt;br /&gt;
    &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.pop(&amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;)&lt;br /&gt;
    for all &amp;lt;tex&amp;gt;a \in \Sigma&amp;lt;/tex&amp;gt;&lt;br /&gt;
      for each &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; split by &amp;lt;tex&amp;gt;S&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;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;R&amp;lt;/tex&amp;gt; in &amp;lt;tex&amp;gt;W&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;W&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;
          else&lt;br /&gt;
            if &amp;lt;tex&amp;gt; |R_1| \le |R_2|&amp;lt;/tex&amp;gt;&lt;br /&gt;
                &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
              else&lt;br /&gt;
                &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.push(&amp;lt;tex&amp;gt;R_2&amp;lt;/tex&amp;gt;)&lt;br /&gt;
==Время работы алгоритма==&lt;br /&gt;
Время работы алгоритма равно &amp;lt;tex&amp;gt;O(|\Sigma| * n\log{n})&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; |\Sigma| * n &amp;lt;/tex&amp;gt;, участвует не более чем в &amp;lt;tex&amp;gt; \log{n}&amp;lt;/tex&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;
&lt;br /&gt;
[[Категория: Теория формальных языков]]&lt;br /&gt;
[[Категория: Автоматы и регулярные языки]]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29994</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29994"/>
				<updated>2013-01-15T12:38:32Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png|470px]]&amp;lt;br&amp;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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;. Для этого можно применить [[Минимизация_ДКА,_алгоритм_за_O(n%5E2)_с_построением_пар_различимых_состояний|алгоритм минимизации ДКА]], который разбивает все состояния на классы эквивалентности.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29993</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29993"/>
				<updated>2013-01-15T12:37:16Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png|470px]]&amp;lt;br&amp;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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;. Для этого можно применить [[Минимизация_ДКА,_алгоритм_за_O(n%5E2)_с_построением_пар_различимых_состояний|алгоритм минимизации ДКА]], который разбивает все состояния на классы эквивалентности.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29992</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29992"/>
				<updated>2013-01-15T12:36:39Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png|470px]]&amp;lt;br&amp;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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;. Для этого можно применить [[Минимизация_ДКА,_алгоритм_за_O(n%5E2)_с_построением_пар_различимых_состояний|алгоритм минимизации ДКА]], который разбивает все состояния автомата на классы эквивалентности.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29991</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29991"/>
				<updated>2013-01-15T12:30:33Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png|470px]]&amp;lt;br&amp;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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29990</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29990"/>
				<updated>2013-01-15T12:30:10Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png|400px]]&amp;lt;br&amp;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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Auto_equiq.png&amp;diff=29989</id>
		<title>Файл:Auto equiq.png</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Auto_equiq.png&amp;diff=29989"/>
				<updated>2013-01-15T12:29:15Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29988</id>
		<title>Эквивалентность состояний ДКА</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9_%D0%94%D0%9A%D0%90&amp;diff=29988"/>
				<updated>2013-01-15T12:28:22Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Проверка ДКА на эквивалентность */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = Два  автомата &amp;lt;tex&amp;gt; \mathcal{A}_1 = \langle Q_1,\Sigma,\delta_1,s_{1}, T_1\subseteq Q_1 \rangle &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathcal{A}_2 = \langle Q_2,\Sigma,\delta_2,s_{2}, T_2\subseteq Q_2 \rangle &amp;lt;/tex&amp;gt; называются '''эквивалентными''', если они распознают один и тот же язык над алфавитом &amp;lt;tex&amp;gt;\Sigma&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;\mathcal{L}(\mathcal{A}_1) = \mathcal{L}(\mathcal{A}_2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Слово &amp;lt;tex&amp;gt;z \in \Sigma^*&amp;lt;/tex&amp;gt; '''различает''' два состояния &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt;, если &lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \notin T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition = Два &amp;lt;em&amp;gt; состояния&amp;lt;/em&amp;gt; &amp;lt;tex&amp;gt;q_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;q_j&amp;lt;/tex&amp;gt; называются '''эквивалентными''' &amp;lt;tex&amp;gt;(q_i \sim q_j)&amp;lt;/tex&amp;gt;, если не существует строки, которая их различает, то есть &amp;lt;tex&amp;gt;\forall z \in \Sigma^*&amp;lt;/tex&amp;gt;  верно, что&lt;br /&gt;
* &amp;lt;tex&amp;gt; \langle q_i, z \rangle \vdash^* \langle t_1, \varepsilon \rangle, \langle q_j, z \rangle \vdash^* \langle t_2, \varepsilon \rangle \Rightarrow (t_1 \in T \Leftrightarrow t_2 \in T) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что эквивалентность состояний действительно является [[Отношение эквивалентности|отношением эквивалентности]]. Так как &amp;lt;tex&amp;gt; \Leftrightarrow &amp;lt;/tex&amp;gt; (равносильность) является отношением эквивалентности и в детерминированном автомате всегда существует путь по любому слову, описанное нами отношение является отношением эквивалентности.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement =&lt;br /&gt;
&amp;lt;tex&amp;gt; \mathcal{A} = \langle Q, \Sigma, \delta, s, T \rangle &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; p_1, p_2, q_1, q_2 \in Q &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; q_i = \delta(p_i, c) &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; w \in \Sigma^*&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2 &amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;cw&amp;lt;/tex&amp;gt; различает &amp;lt;tex&amp;gt; p_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; p_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof = &lt;br /&gt;
&amp;lt;tex&amp;gt; \langle p_i, cw \rangle \vdash \langle q_i, w \rangle \vdash^* \langle t_i, \varepsilon \rangle &amp;lt;/tex&amp;gt;&lt;br /&gt;
А значит, по условию различимости для &amp;lt;tex&amp;gt; q_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; q_2&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt; t_1 \in T \Leftrightarrow t_2 \notin T &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Пример ===&lt;br /&gt;
[[Файл:avtomat2.png|350px]] [[Файл:avtomat3.png|350px]]&lt;br /&gt;
&lt;br /&gt;
Эти два автомата принимают слова из языка слов длины не меньше одного, состоящих из символов алфавита &amp;lt;tex&amp;gt; \lbrace 0, 1\rbrace &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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt; со стартовым состоянием &amp;lt;tex&amp;gt; s_2 &amp;lt;/tex&amp;gt; соответственно. Нужно проверить их на эквивалентность.&lt;br /&gt;
Для этого построим автомат &amp;lt;tex&amp;gt; \mathcal{A} &amp;lt;/tex&amp;gt;, содержащий все состояния обоих автоматов и изначальные переходы между ними:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:auto_equiq.png]]&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; \mathcal{A}_1 &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; \mathcal{A}_2 &amp;lt;/tex&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%96%D0%BE%D1%80%D0%B4%D0%B0%D0%BD%D0%B0&amp;diff=27086</id>
		<title>Обсуждение:Теорема Жордана</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%96%D0%BE%D1%80%D0%B4%D0%B0%D0%BD%D0%B0&amp;diff=27086"/>
				<updated>2012-06-26T17:41:23Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Правда ли, что &amp;lt;tex&amp;gt;\|f\|_c&amp;lt;/tex&amp;gt; {{---}} супремум? --[[Участник:Komarov|Андрей Комаров]] 21:41, 25 июня 2012 (GST)&lt;br /&gt;
: Правда --[[Участник:Komarov|Андрей Комаров]] 21:43, 25 июня 2012 (GST)&lt;br /&gt;
:: Спасибо! --[[Участник:Komarov|Андрей Комаров]] 21:43, 25 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
В первом утверждении бред на бреде и бредом погоняет. В условии — суммы Фейера, а в доказательстве — частичные суммы. Рассматривается норма функции, не являющейся непрерывной, в пространстве непрерывных функций. Полиномом наилучшего приближения &amp;lt;tex&amp;gt; f &amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt; C &amp;lt;/tex&amp;gt; является обычный полином, а не тригонометрический, соответственно, теорема Вейерштрасса для него неприменима. Переход от модуля к норме тоже какой-то мутный. Что делать будем? --[[Участник:Sementry|Мейнстер Д.]] 20:03, 26 июня 2012 (GST)&lt;br /&gt;
: \sigma (f) — ряд Фурье, а не суммы Фейера. И Виталя с Артемом говорят, что то, что мы берем норму || ||_C у функции не в C — это нормально.--[[Участник:Dgerasimov|Дмитрий Герасимов]] 20:40, 26 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
Ребят, мне кажется, или доказательство утверждения про равномерную сходимость ряда фурье нифига не расписано? --[[Участник:System29a|System29a]] 21:07, 26 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
формулировка какая-то мутная&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;f\in CV &amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt; f &amp;lt;/tex&amp;gt; — непрерывная, ограниченной вариации). Тогда &amp;lt;tex&amp;gt; \forall x: f&amp;lt;/tex&amp;gt; раскладывается в равномерно сходящийся ряд Фурье.&lt;br /&gt;
}}&lt;br /&gt;
что значит &amp;lt;tex&amp;gt; \forall x: f&amp;lt;/tex&amp;gt;? кроме того, указано, что ряд фурье равномерно сходится, но не указано, на каком промежутке. [[Участник:Glukos|Иван Раков]] 21:40, 26 июня 2012 (GST)&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%96%D0%BE%D1%80%D0%B4%D0%B0%D0%BD%D0%B0&amp;diff=27085</id>
		<title>Обсуждение:Теорема Жордана</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%96%D0%BE%D1%80%D0%B4%D0%B0%D0%BD%D0%B0&amp;diff=27085"/>
				<updated>2012-06-26T17:40:15Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Правда ли, что &amp;lt;tex&amp;gt;\|f\|_c&amp;lt;/tex&amp;gt; {{---}} супремум? --[[Участник:Komarov|Андрей Комаров]] 21:41, 25 июня 2012 (GST)&lt;br /&gt;
: Правда --[[Участник:Komarov|Андрей Комаров]] 21:43, 25 июня 2012 (GST)&lt;br /&gt;
:: Спасибо! --[[Участник:Komarov|Андрей Комаров]] 21:43, 25 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
В первом утверждении бред на бреде и бредом погоняет. В условии — суммы Фейера, а в доказательстве — частичные суммы. Рассматривается норма функции, не являющейся непрерывной, в пространстве непрерывных функций. Полиномом наилучшего приближения &amp;lt;tex&amp;gt; f &amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt; C &amp;lt;/tex&amp;gt; является обычный полином, а не тригонометрический, соответственно, теорема Вейерштрасса для него неприменима. Переход от модуля к норме тоже какой-то мутный. Что делать будем? --[[Участник:Sementry|Мейнстер Д.]] 20:03, 26 июня 2012 (GST)&lt;br /&gt;
: \sigma (f) — ряд Фурье, а не суммы Фейера. И Виталя с Артемом говорят, что то, что мы берем норму || ||_C у функции не в C — это нормально.--[[Участник:Dgerasimov|Дмитрий Герасимов]] 20:40, 26 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
Ребят, мне кажется, или доказательство утверждения про равномерную сходимость ряда фурье нифига не расписано? --[[Участник:System29a|System29a]] 21:07, 26 июня 2012 (GST)&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;f\in CV &amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt; f &amp;lt;/tex&amp;gt; — непрерывная, ограниченной вариации). Тогда &amp;lt;tex&amp;gt; \forall x: f&amp;lt;/tex&amp;gt; раскладывается в равномерно сходящийся ряд Фурье.&lt;br /&gt;
|proof=&lt;br /&gt;
Применим прошлую теорему. Получим, что сходится к числу &amp;lt;tex&amp;gt;\frac{f(x+0)+f(x-0)}{2}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как функция непрерывна, &amp;lt;tex&amp;gt;f(x+0)=f(x-0)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
что значит &amp;lt;tex&amp;gt; \forall x: f&amp;lt;/tex&amp;gt;? кроме того, указано, что ряд фурье равномерно сходится, но не указано, на каком промежутке.[[Участник:Glukos|Иван Раков]] 21:40, 26 июня 2012 (GST)&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26284</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26284"/>
				<updated>2012-06-21T10:46:05Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец Z-блока строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальной позицией конца &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; (среди всех таких Z-блоков, если их несколько, выбирается наибольший). Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. В данном случае будет определено корректное значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt; в силу того, что оно определяется наивно, путем сравнения с начальными символами строки.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто наивно пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. Корректность в таком случае также гарантированна.&lt;br /&gt;
Иначе мы уже знаем верное значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26280</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26280"/>
				<updated>2012-06-21T10:24:51Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец Z-блока строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальной позицией конца &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; (среди всех таких Z-блоков, если их несколько, выбирается наибольший). Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26279</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26279"/>
				<updated>2012-06-21T10:23:50Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего Z-блока строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальной позицией конца &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26278</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26278"/>
				<updated>2012-06-21T10:23:36Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
&amp;lt;br&amp;gt;Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего Z-блока строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальной позицией конца &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26277</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26277"/>
				<updated>2012-06-21T10:21:29Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего префикса строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальным значением &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26276</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26276"/>
				<updated>2012-06-21T10:21:12Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Алгоритм поиска */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего префикса строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальным значением &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Z-блоком назовем подстроку с началом в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и длиной &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26270</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26270"/>
				<updated>2012-06-21T10:05:24Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Определение */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
'''Примечание:''' далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего префикса строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальным значением &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26268</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26268"/>
				<updated>2012-06-21T10:05:09Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Определение */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Примечание: далее в конспекте символы строки нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего префикса строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальным значением &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26265</id>
		<title>Z-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=26265"/>
				<updated>2012-06-21T10:04:29Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Определение */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
Z-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — это длина максимального префикса подстроки, начинающейся с позиции &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, который одновременно является и префиксом всей строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;. Значение &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.&lt;br /&gt;
[[Файл:Zfunc-examp.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Алгоритм поиска==&lt;br /&gt;
Для работы алгоритма заведём две переменные: &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; — начало и конец наибольшего префикса строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; с максимальным значением &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Изначально &amp;lt;tex&amp;gt;left=0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть нам известны значения Z-функции от &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt;. Найдём &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Рассмотрим два случая.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
1) &amp;lt;tex&amp;gt;i &amp;gt; right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Просто пробегаемся по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и сравниваем символы на позициях &amp;lt;tex&amp;gt;S[i+j]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S[j]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; первая позиция в строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; для которой не выполняется равенство &amp;lt;tex&amp;gt;S[i+j] == S[j]&amp;lt;/tex&amp;gt;, тогда &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; это и Z-функция для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;. Тогда &amp;lt;tex&amp;gt;left = i, right = i + j - 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
2) &amp;lt;tex&amp;gt;i \le right&amp;lt;/tex&amp;gt;:&amp;lt;br&amp;gt;&lt;br /&gt;
Сравним &amp;lt;tex&amp;gt;Z[i - left] + i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; меньше, то надо просто пробежаться по строке начиная с позиции &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и вычислить значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Иначе мы уже знаем значение &amp;lt;tex&amp;gt;Z[i]&amp;lt;/tex&amp;gt;, так как оно равно значению &amp;lt;tex&amp;gt;Z[i - left]&amp;lt;/tex&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:z-func.png]]&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Этот алгоритм работает за &amp;lt;tex&amp;gt;O(\lvert S\rvert)&amp;lt;/tex&amp;gt;, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от &amp;lt;tex&amp;gt;left&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;right&amp;lt;/tex&amp;gt; и при высчитывании &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;-функции простым циклом.&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
 '''Zfunction'''(p) &lt;br /&gt;
    answer[0] = 0&lt;br /&gt;
    left = 0&lt;br /&gt;
    right = 0&lt;br /&gt;
    '''for''' (i = 1..(n - 1))&lt;br /&gt;
       '''if''' (i &amp;gt; right)&lt;br /&gt;
          j = 0&lt;br /&gt;
          '''while''' (i + j &amp;lt; n &amp;amp;&amp;amp; p[i + j] == p[j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = j&lt;br /&gt;
          left = i&lt;br /&gt;
          right = i + j - 1&lt;br /&gt;
       '''else if''' (answer[i - left] &amp;lt; right - i + 1)&lt;br /&gt;
          answer[i] = answer[i - left]&lt;br /&gt;
       '''else''' &lt;br /&gt;
          j = 1&lt;br /&gt;
          '''while''' (j + right &amp;lt; n &amp;amp;&amp;amp; p[j + right - i] == p[right + j])&lt;br /&gt;
             j++&lt;br /&gt;
          answer[i] = right + j - i&lt;br /&gt;
          left = i&lt;br /&gt;
          right = right + j - 1&lt;br /&gt;
    '''return''' answer&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
[http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://ru.wikipedia.org/wiki/Z-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F Z-функция — Википедия]&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26262</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26262"/>
				<updated>2012-06-21T10:02:14Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|640px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Префикс-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; строится за &amp;lt;tex&amp;gt;O(S) = O(P + T)&amp;lt;/tex&amp;gt;. Проход цикла по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; содержит &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; итераций. Итого, время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(P + T)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(P+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;p + 1&amp;lt;/tex&amp;gt; (т.е. до начала цепочки &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26260</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26260"/>
				<updated>2012-06-21T10:01:12Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Оценка по памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Префикс-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; строится за &amp;lt;tex&amp;gt;O(S) = O(P + T)&amp;lt;/tex&amp;gt;. Проход цикла по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; содержит &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; итераций. Итого, время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(P + T)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(P+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;p + 1&amp;lt;/tex&amp;gt; (т.е. до начала цепочки &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26258</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26258"/>
				<updated>2012-06-21T09:59:54Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Оценка по памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Префикс-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; строится за &amp;lt;tex&amp;gt;O(S) = O(P + T)&amp;lt;/tex&amp;gt;. Проход цикла по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; содержит &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; итераций. Итого, время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(P + T)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(P+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;p + 1&amp;lt;/tex&amp;gt; (т.е. до начала цепочки &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26257</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26257"/>
				<updated>2012-06-21T09:59:34Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Оценка по памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Префикс-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; строится за &amp;lt;tex&amp;gt;O(S) = O(P + T)&amp;lt;/tex&amp;gt;. Проход цикла по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; содержит &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; итераций. Итого, время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(P + T)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(P+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;p + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26256</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26256"/>
				<updated>2012-06-21T09:58:33Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Время работы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
Префикс-функция от строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; строится за &amp;lt;tex&amp;gt;O(S) = O(P + T)&amp;lt;/tex&amp;gt;. Проход цикла по строке &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; содержит &amp;lt;tex&amp;gt;O(T)&amp;lt;/tex&amp;gt; итераций. Итого, время работы алгоритма оценивается как &amp;lt;tex&amp;gt;O(P + T)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26254</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26254"/>
				<updated>2012-06-21T09:56:14Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Псевдокод */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == p)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26253</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26253"/>
				<updated>2012-06-21T09:55:48Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Псевдокод */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p = |P|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (t - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(p + i + 1) == t)&lt;br /&gt;
       answer[count++] = i + 1 - p&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26252</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26252"/>
				<updated>2012-06-21T09:53:30Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |P|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;s = |S|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (s - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(t + i + 1) == t)&lt;br /&gt;
       answer[count++] = i + 1 - t&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26251</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26251"/>
				<updated>2012-06-21T09:53:01Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;P&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;S = P\#T&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |T|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |P|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |P|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |P| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|P| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;s = |S|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (s - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(t + i + 1) == t)&lt;br /&gt;
       answer[count++] = i + 1 - t&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26250</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26250"/>
				<updated>2012-06-21T09:51:05Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Алгоритм Кнута — Морриса — Пратта — алгоритм [[Наивный алгоритм поиска подстроки в строке#Постановка задачи|поиска подстроки в строке]].&lt;br /&gt;
&lt;br /&gt;
==Описание алгоритма==&lt;br /&gt;
Дана цепочка &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и образец &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;P = T\#S&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |T|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |T|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |T|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |T| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |T| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|T| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|600px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;s = |S|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (s - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(t + i + 1) == t)&lt;br /&gt;
       answer[count++] = i + 1 - t&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26249</id>
		<title>Алгоритм Кнута-Морриса-Пратта</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9A%D0%BD%D1%83%D1%82%D0%B0-%D0%9C%D0%BE%D1%80%D1%80%D0%B8%D1%81%D0%B0-%D0%9F%D1%80%D0%B0%D1%82%D1%82%D0%B0&amp;diff=26249"/>
				<updated>2012-06-21T09:50:54Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Алгоритм Кнута — Морриса — Пратта — алгоритм [[Наивный алгоритм поиска подстроки в строке#Постановка задачи|поиска подстроки в строке]].&lt;br /&gt;
&lt;br /&gt;
==Описание алгоритма==&lt;br /&gt;
Дана цепочка &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и образец &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Требуется найти все позиции, начиная с которых &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; входит в &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Построим строку &amp;lt;tex&amp;gt;P = T\#S&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; — любой символ, не входящий в алфавит &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Посчитаем на ней [[Префикс-функция|префикс-функцию]] &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt;. Благодаря разделительному символу &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt;, выполняется &amp;lt;tex&amp;gt;\forall i: \pi(i) \le |T|&amp;lt;/tex&amp;gt;. Заметим, что по определению [[Префикс-функция|префикс-функции]] при &amp;lt;tex&amp;gt;i &amp;gt; |T|&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\pi(i) = |T|&amp;lt;/tex&amp;gt; подстроки длины &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;, начинающиеся с позиций &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;i - |T| + 1&amp;lt;/tex&amp;gt;, совпадают. Соберем все такие позиции &amp;lt;tex&amp;gt;i - |T| + 1&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, вычтем из каждой позиции &amp;lt;tex&amp;gt;|T| + 1&amp;lt;/tex&amp;gt;, это и будет ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:kmp_pict.png|500px]]&lt;br /&gt;
&lt;br /&gt;
==Псевдокод==&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;t = |T|&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;s = |S|&amp;lt;/tex&amp;gt;.&lt;br /&gt;
 count = 0&lt;br /&gt;
 '''for''' (i = 0 .. (s - 1))&lt;br /&gt;
    '''if''' (&amp;lt;tex&amp;gt;\pi&amp;lt;/tex&amp;gt;(t + i + 1) == t)&lt;br /&gt;
       answer[count++] = i + 1 - t&lt;br /&gt;
&lt;br /&gt;
==Время работы==&lt;br /&gt;
&amp;lt;tex&amp;gt;O(s + t)&amp;lt;/tex&amp;gt; (время подсчета &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для &amp;lt;tex&amp;gt;P) + O(s)&amp;lt;/tex&amp;gt; (последующий &amp;lt;tex&amp;gt;for&amp;lt;/tex&amp;gt;) &amp;lt;tex&amp;gt;= O(s + t)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Оценка по памяти==&lt;br /&gt;
Предложенная реализация имеет оценку по памяти &amp;lt;tex&amp;gt;O(S+T)&amp;lt;/tex&amp;gt;. Оценки &amp;lt;tex&amp;gt;O(S)&amp;lt;/tex&amp;gt; можно добиться за счет незапоминания значений &amp;lt;tex&amp;gt;\pi()&amp;lt;/tex&amp;gt; для позиций в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, меньших &amp;lt;tex&amp;gt;t + 1&amp;lt;/tex&amp;gt; (до начала цепочки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==Источники==&lt;br /&gt;
[http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm Knuth–Morris–Pratt algorithm]&amp;lt;br&amp;gt;&lt;br /&gt;
Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн — Алгоритмы: построение и анализ / пер. с англ. — изд. 2-е — М.: Издательский дом «Вильямс», 2009. — с.1036. — ISBN 978-5-8459-0857-5.&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Kmp_pict.png&amp;diff=26248</id>
		<title>Файл:Kmp pict.png</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Kmp_pict.png&amp;diff=26248"/>
				<updated>2012-06-21T09:49:22Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: загружена новая версия «Файл:Kmp pict.png»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24280</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24280"/>
				<updated>2012-06-07T05:15:32Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Одномерный случай */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из пространства &amp;lt;tex&amp;gt;\mathbb R^n&amp;lt;/tex&amp;gt;. Пусть односвязная область &amp;lt;tex&amp;gt;R \subset \mathbb R^n&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным прямым. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые лежат между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24279</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24279"/>
				<updated>2012-06-07T05:15:04Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из пространства &amp;lt;tex&amp;gt;\mathbb R^n&amp;lt;/tex&amp;gt;. Пусть односвязная область &amp;lt;tex&amp;gt;R \subset \mathbb R^n&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным прямым. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24278</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24278"/>
				<updated>2012-06-07T05:14:28Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из пространства &amp;lt;tex&amp;gt;\mathbb R^n&amp;lt;/tex&amp;gt;. Пусть односвязная область &amp;lt;tex&amp;gt;R \subset \mathbb R^n&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным осям. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24277</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24277"/>
				<updated>2012-06-07T05:13:24Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из евклидового пространства. Пусть односвязная область &amp;lt;tex&amp;gt;R \subset S&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным осям. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24276</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24276"/>
				<updated>2012-06-07T05:11:56Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из евклидового пространства. Пусть область &amp;lt;tex&amp;gt;R \subset S&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным осям. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24275</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24275"/>
				<updated>2012-06-07T05:11:36Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
Пусть задано множество точек &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; из евклидового пространства. Пусть область &amp;lt;tex&amp;gt;R \subset S&amp;lt;/tex&amp;gt; такова, что её границы ортогональны координатным осям пространства. Требуется определить множество точек &amp;lt;tex&amp;gt;S' \subset S&amp;lt;/tex&amp;gt;, лежащих в области &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt;.&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24274</id>
		<title>Ортогональный поиск</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA&amp;diff=24274"/>
				<updated>2012-06-07T05:04:42Z</updated>
		
		<summary type="html">&lt;p&gt;Glukos: /* Одномерный случай */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{В разработке}}&lt;br /&gt;
== Одномерный случай ==&lt;br /&gt;
&lt;br /&gt;
Пусть дана прямая с точками на ней и отрезок. Необходимо указать, какие из точек лежат на этом отрезке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Line_with_dots_and_segment.png‎]]&amp;lt;br&amp;gt;&lt;br /&gt;
Задача тривиальна — нужно оставить только те точки, которые находятся между началом и концом отрезка.&lt;br /&gt;
&lt;br /&gt;
На практике для быстрого осуществления запроса нужно хранить точки в отсортированном массиве и пользоваться двоичным поиском. В C++ данная задача решается с помощью функций из STL - upper_bound и lower_bound.&lt;br /&gt;
&lt;br /&gt;
lower_bound возвращает итератор на первый элемент, больший либо равный данного. &amp;lt;br&amp;gt;&lt;br /&gt;
upper_bound возвращает итератор на первый элемент множества со значением, большим данного.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим на примере:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Upper_bound_and_lower_bound2.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Код реализации:&lt;br /&gt;
&lt;br /&gt;
 template&amp;lt;class RauIter, class OutIter, class Scalar&amp;gt; OutIter range_search(RauIter p, RauIter q, OutIter out)&lt;br /&gt;
 {&lt;br /&gt;
    return std::copy(lower_bound(p, q, l), upper_bound(p, q, r), out);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Алгоритм работает за &amp;lt;tex&amp;gt;O(\log n)&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;(x, y)&amp;lt;/tex&amp;gt; из множества. В качестве ключа будет использоваться &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата точки. Теперь модернизируем дерево: в каждой вершине дерева будем хранить отсортированный по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координате массив точек, которые содержатся в соответствующем поддереве. &amp;lt;br&amp;gt;Рассмотрим на примере:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree.png|600px]]&amp;lt;br&amp;gt;&lt;br /&gt;
Рассмотрим, как в такой структуре данных будет выглядеть поиск множества точек, находящихся в заданном прямоугольнике &amp;lt;tex&amp;gt;[x_{min}, x_{max}] \times [y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для начала, найдем в дереве те точки, &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координата которых лежит в интервале &amp;lt;tex&amp;gt;[x_{min}, x_{max}]&amp;lt;/tex&amp;gt;. Сделаем это следующим образом: &lt;br /&gt;
# Найдем в дереве поиска вершины с минимальной и максимальной &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;-координатой из прямоугольника запроса, добавим их в искомое множество, обозначим их как &amp;lt;tex&amp;gt;v_l&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;v_r&amp;lt;/tex&amp;gt;. &lt;br /&gt;
# Добавим в искомое множество их наименьшего общего предка &amp;lt;tex&amp;gt;v_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для каждой из промежуточных вершин &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt; на восходящем пути &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; зафиксируем, из какого ребенка мы поднялись в вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если мы поднялись из левого сына, то добавим в искомое множество саму вершину &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;, а также множество точек, находящихся в поддереве правого сына вершины &amp;lt;tex&amp;gt;v_i&amp;lt;/tex&amp;gt;. Если же мы поднялись из правого сына, то не добавляем ничего.&lt;br /&gt;
# Повторим процесс для пути &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Здесь ориентация сторон инвертирована: будем пополнять множество в том случае, если мы поднялись из правого сына.&lt;br /&gt;
Пример процесса показан на иллюстрации:&amp;lt;br&amp;gt;&lt;br /&gt;
[[Файл:ortog_search_tree2.png|700px]]&amp;lt;br&amp;gt;&lt;br /&gt;
В итоге, в множество мы добавим &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; вершин и &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; поддеревьев дерева поиска. Теперь нужно просеять полученное множество — извлечь из него те элементы, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата которых не лежит в интервале &amp;lt;tex&amp;gt;[y_{min}, y_{max}]&amp;lt;/tex&amp;gt;. Для точек это сделать просто — нужно вручную проверить, лежит ли &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;-координата в нужном интервале. Для каждого из полученных поддеревьев обратимся к массиву содержащихся в нем точек и запустим от него приведенную выше функцию &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt;. Все полученные таким образом точки и будут составлять ответ.&lt;br /&gt;
&amp;lt;br&amp;gt;Каждая из функций &amp;lt;tex&amp;gt;range{\_}search(y_{min}, y_{max})&amp;lt;/tex&amp;gt; будет работать в худшем случае за &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;, отсюда получаем итоговое время выполнения запроса &amp;lt;tex&amp;gt;O(\log^2 n)&amp;lt;/tex&amp;gt;. Что касается памяти, то в сбалансированном дереве поиска &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt; слоев, а каждый слой хранит массивы, содержащие в сумме ровно &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; точек, соответственно вся структура в целом занимает &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
== Обобщение для p-мерного пространства ==&lt;br /&gt;
&lt;br /&gt;
Такую структуру данных можно при необходимости обобщить на случай большей размерности. Пусть у нас есть множество точек из &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;-мерного пространства, каждая из которых представляется как &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; координатных чисел: &amp;lt;tex&amp;gt;(\xi_1, \xi_2, ... , \xi_p)&amp;lt;/tex&amp;gt;. Тогда, строя дерево поиска по координате &amp;lt;tex&amp;gt;\xi_i&amp;lt;/tex&amp;gt;, в каждой вершине будем хранить другое дерево поиска с ключом &amp;lt;tex&amp;gt;\xi_{i+1}&amp;lt;/tex&amp;gt;, составленное из точек, лежащих в соответствующем поддереве. В дереве поиска, составленном по предпоследней координате &amp;lt;tex&amp;gt;\xi_{p-1}&amp;lt;/tex&amp;gt;, уже не будет необходимости хранить в каждой вершине целое дерево, поскольку при переходе на последнюю координату &amp;lt;tex&amp;gt;\xi_{p}&amp;lt;/tex&amp;gt; дальнейший поиск производиться не будет, поэтому в вершинах будем хранить массивы, так же, как и в двумерном случае. Оценим занимаемую память и время запроса: при добавлении следующей координаты асимптотика обеих величин умножается на  &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Отсюда, получаем оценку &amp;lt;tex&amp;gt;O(\log^{p} n)&amp;lt;/tex&amp;gt; на время запроса и &amp;lt;tex&amp;gt;O(n\log^{p-1} n)&amp;lt;/tex&amp;gt; на занимаемую память.&lt;br /&gt;
&lt;br /&gt;
Такой же результат можно получить с помощью [[Сжатое многомерное дерево отрезков|сжатого многомерного дерева отрезков]].&lt;br /&gt;
&lt;br /&gt;
== Ускорение запроса ==&lt;br /&gt;
Для ускорения запроса можно &amp;quot;прошить&amp;quot; дерево поиска по предпоследней координате, а именно: каждый элемент массива, сохраненного в какой-либо вершине, соединить с элементами массивов, сохраненных в вершинах-детях. Соединять будем по следующему принципу: элемент &amp;lt;tex&amp;gt;(x, y)&amp;lt;/tex&amp;gt; массива-предка соединим с элементами &amp;lt;tex&amp;gt;upper\_bound(y)&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound(y)&amp;lt;/tex&amp;gt; каждого массива-ребенка. Ниже представлен пример соединения корня с его левым сыном:&amp;lt;br&amp;gt;[[Файл:ortog_search_tree3.png]]&amp;lt;br&amp;gt;Для выполнения завершающей фазы поиска нам достаточно будет посчитать &amp;lt;tex&amp;gt;upper\_bound()&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;lower\_bound()&amp;lt;/tex&amp;gt; только на массиве, привязанному к корню дерева. Для получения границ на других массивах можно будет просто спуститься по ссылкам. Заметим, что все вершины, к массивам которых нужно перейти, смежны с какой-либо из вершин путей &amp;lt;tex&amp;gt;v_l \to v_n&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;v_r \to v_n&amp;lt;/tex&amp;gt;. Отсюда следует, что число спусков оценивается как &amp;lt;tex&amp;gt;O(length(v_l \to v_n) + length(v_r \to v_n)) = O(\log n)&amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;Таким образом, поиск теперь будет выполняться за &amp;lt;tex&amp;gt;O(\log^{p-1} n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; — размерность пространства.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
== Квадро дерево ==&lt;br /&gt;
&lt;br /&gt;
== Инкрементальное квадро дерево == &lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Glukos</name></author>	</entry>

	</feed>