Редактирование: Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))

Перейти к: навигация, поиск

Внимание! Вы не авторизовались на сайте. Ваш IP-адрес будет публично видимым, если вы будете вносить любые правки. Если вы войдёте или создадите учётную запись, правки вместо этого будут связаны с вашим именем пользователя, а также у вас появятся другие преимущества.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 15: Строка 15:
  
 
Итеративно строим разбиение множества состояний следующим образом.
 
Итеративно строим разбиение множества состояний следующим образом.
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>).
+
# Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний <tex>Q \setminus F</tex>.
# Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>\langle F,\ c \rangle</tex> и <tex>\langle Q \setminus F, c \rangle</tex> помещаются в очередь.
+
# Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>(F, c)</tex> и <tex>(Q \setminus F, c)</tex> помещаются в очередь.
# Из очереди извлекается пара <tex>\langle C,\ a \rangle</tex>, <tex>C</tex> далее именуется как сплиттер.
+
# Из очереди извлекается пара <tex>(C, a)</tex>, <tex>C</tex> далее именуется как мастер Сплиттер.
# Каждый класс <tex>R</tex> текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер <tex>(R_1)</tex>, а второй из всех оставшихся <tex>(R_2)</tex>.  
+
# Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер, а второй из всех оставшихся.  
# Если <tex>R</tex> разбился на два непустых подкласса (то есть <tex> R_1 \ne \emptyset \ \land \  R_2 \ne \emptyset </tex>).
+
# Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а подклассы добавляются в очередь.
## В разбиении <tex>P</tex> класс <tex>R</tex> заменяется на свои подклассы <tex>R_1</tex> и <tex>R_2</tex>.
 
## Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>\langle R_1, c \rangle</tex> и <tex>\langle R_2, c \rangle</tex> помещаются в очередь.
 
 
# Пока очередь не пуста, выполняем п.3 – п.5.
 
# Пока очередь не пуста, выполняем п.3 – п.5.
  
 
===Псевдокод===
 
===Псевдокод===
*<tex>\mathtt{Q}</tex> {{---}} множество состояний ДКА,
+
<tex>Q</tex> {{---}} множество состояний ДКА.
*<tex>\mathtt{F}</tex> {{---}} множество терминальных состояний,
+
<tex>F</tex> {{---}} множество терминальных состояний.
*<tex>\mathtt{\delta}</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{---}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>),
+
<tex>S</tex> {{---}} очередь пар <tex>(C, a)</tex>.
*<tex>\mathtt{S}</tex> {{---}} очередь пар <tex>\langle C,\ a \rangle</tex>,
+
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
*<tex>\mathtt{P}</tex> {{---}} разбиение множества состояний ДКА,
+
<tex>R</tex> {{---}} класс состояний ДКА.
*<tex>\mathtt{R}</tex> {{---}} класс состояний ДКА.
+
   <tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
 
+
  <tex>\mathtt{S} \leftarrow \varnothing </tex>
   '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector'''
+
  '''for''' <tex>c \in \Sigma</tex>
    <tex>\mathtt{P} \leftarrow \{ F,\ Q \setminus F \}</tex>
+
    '''insert''' <tex>(\mathtt{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex>
    <tex>\mathtt{S} \leftarrow \varnothing </tex>
+
    '''insert''' <tex>(\mathtt{Q} \setminus \mathtt{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex>
    '''for''' <tex>c \in \Sigma</tex>
+
  '''while''' <tex> \mathtt{S} \ne \varnothing </tex>
      push <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{S}</tex>
+
    <tex>(C, a) \leftarrow</tex> '''pop'''(<tex>\mathtt{S}</tex>)
    '''while''' <tex>\mathtt{S} \ne \varnothing</tex>
+
    '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>  
      <tex>\langle C,\ a \rangle</tex> <tex>\leftarrow</tex> pop '''from''' <tex>\mathtt{S}</tex>
+
      <tex>R_1 = R \cap \delta^{-1} (C, a) </tex>
      '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>  
+
      <tex>R_2 = R \setminus R_1</tex>
        <tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C,\ a)</tex>
+
      '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex>
        '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex>
+
        '''replace''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
          replace <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> with <tex>R_1</tex> '''and''' <tex>R_2</tex>
+
        '''for''' <tex> c \in \Sigma </tex>  
          '''for''' <tex>c \in \Sigma</tex>
+
          '''insert''' <tex>(R_1, c)</tex> '''in''' <tex>\mathtt{S}</tex>
            insert <tex>\langle R_1,\ c \rangle</tex> '''in''' <tex>\mathtt{S}</tex>
+
          '''insert''' <tex>(R_2, c)</tex> '''in''' <tex>\mathtt{S}</tex>
            insert <tex>\langle R_2,\ c \rangle</tex> '''in''' <tex>\mathtt{S}</tex>
 
    '''return''' <tex>\mathtt{P}</tex>
 
 
 
 
Когда очередь <tex>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
 
Когда очередь <tex>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
  
 
===Время работы===
 
===Время работы===
Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex> {{---}} алфавит. Это следует из того, что если пара <tex>\langle C,\ a \rangle</tex> попала в очередь, и класс <tex>C</tex> использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса <tex>C_1</tex> и <tex>C_2</tex>, причем можно гарантировать лишь следующее уменьшение размера: <tex>|C| \geqslant |C_i| + 1</tex>. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем <tex>O(n)</tex> раз. Учитывая, что ребер всего <tex>O(|\Sigma| \cdot n)</tex>, получаем указанную оценку.
+
Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex>{{---}} алфавит. Это следует из того, что если пара <tex>(C, a)</tex> попала в очередь, и класс <tex>C</tex> использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса <tex>C_1</tex> и <tex>C_2</tex>, причем можно гарантировать лишь следующее уменьшение размера: <tex>|C| \ge |C_i| + 1</tex>. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем <tex>O(n)</tex> раз. Учитывая, что ребер всего <tex>O(|\Sigma| \cdot n)</tex>, получаем указанную оценку.
  
 
== Алгоритм Хопкрофта==
 
== Алгоритм Хопкрофта==
Рассмотрим алгоритм, позволяющий решить задачу быстрее, чем за <tex> O(n^2) </tex>.
 
  
 
{{Лемма
 
{{Лемма
Строка 60: Строка 54:
 
|proof =  
 
|proof =  
 
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
 
Разобьем все классы с помощью <tex>R </tex> и <tex> R_1</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \in R_1 \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \in R_1</tex> or
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R \ \land \  \delta(r, a) \notin R_1 \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R</tex> and <tex> \delta(r, a) \notin R_1</tex> or
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R \ \land \  \delta(r, a) \notin R_1</tex>   
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex> and <tex> \delta(r, a) \notin R_1</tex>   
 
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
 
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_2 </tex> or
 
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex>  
 
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R_2</tex>  
 
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/>
 
Из этого следует, что разбиение всех классов с помощью <tex>R_2</tex> никак не повлияет на текущее разбиение. <br/>
 
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/>
 
Аналогично доказывается и для разбиения с помощью <tex>R </tex> и <tex> R_2</tex> по символу <tex>a</tex>. <br/>
 
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
 
Разобьем все классы с помощью <tex>R_1</tex> и <tex> R_2</tex> по символу <tex>a</tex>, тогда для любого класса <tex>B</tex> из текущего разбиения выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1 \ \land \  \delta(r, a) \notin R_2 \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R_1</tex> and <tex> \delta(r, a) \notin R_2</tex> or
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \in R_2 \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \in R_2</tex> or
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1 \ \land \  \delta(r, a) \notin R_2</tex>   
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R_1</tex> and <tex> \delta(r, a) \notin R_2</tex>   
 
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
 
А так как <tex>R = R_1 \cup R_2</tex> и <tex>R_1 \cap R_2 = \varnothing</tex> то выполняется
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R \ \lor</tex>
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or
:<tex>\forall r \in B \,\,\, \delta(r, a) \notin R</tex>  
+
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex>  
 
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.
 
Из этого следует, что разбиение всех классов с помощью <tex>R</tex> никак не повлияет на текущее разбиение.
 
}}
 
}}
  
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет пары в очередь.
+
Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь.
После замены класса <tex>R</tex> в разбиении <tex>P</tex> на его подклассы <tex>R_1</tex> и <tex>R_2</tex>, как и раньше перебираем символы алфавита <tex>c \in \Sigma</tex>.
+
Если класс <tex>R</tex> уже есть в очереди, то согласно лемме можно просто заменить его на <tex>R_1</tex> и <tex>R_2</tex>.  
 
+
Если класса <tex>R</tex> нет в очереди, то согласно лемме в очередь можно добавить класс <tex>R</tex> и любой из <tex>R_1</tex> и <tex>R_2</tex>, а так как для любого класса <tex>B</tex> из текущего разбиения выполняется
Если пара <tex>\langle R,\ c \rangle</tex> уже есть в очереди, то согласно лемме можно просто заменить её на пары <tex>\langle R_1, c \rangle</tex> и <tex>\langle R_2, c \rangle</tex>.
+
:<tex>\forall r \in B \,\,\, \delta(r, a) \in R </tex> or
 
+
:<tex> \forall r \in B \,\,\, \delta(r, a) \notin R</tex>  
Если пары <tex>\langle R,\ c \rangle</tex> нет в очереди, то достаточно добавить любую из пар <tex>\langle R_1, c \rangle</tex> и <tex>\langle R_2, c \rangle</tex>. Это следует из следующих соображений: <tex>R</tex> может быть в разбиении только если в очередь были положены пары <tex>\langle R,\ a \rangle</tex> для <tex>\forall a \in \Sigma</tex>, а поскольку в очереди пары <tex>\langle R,\ c \rangle</tex> нет, то  мы её уже успели рассмотреть, следовательно классы из разбиения <tex>P</tex> уже были разбиты по <tex>\langle R,\ c \rangle</tex>.
+
то в очередь можно добавить только меньшее из <tex>R_1</tex> и <tex>R_2</tex>.
 
 
=== Реализация ===
 
 
 
*<tex>\mathtt{Q}</tex> {{---}} множество состояний ДКА,
 
*<tex>\mathtt{F}</tex> {{---}} множество терминальных состояний,
 
*<tex>\mathtt{\delta}</tex> {{---}} функция перехода (<tex>\delta (r,\ a)</tex> {{---}} состояние, в которое можно совершить переход из <tex>r</tex> по символу <tex>a</tex>),
 
*<tex>\mathtt{S}</tex> {{---}} очередь пар <tex>\langle C,\ a \rangle</tex>,
 
*<tex>\mathtt{P}</tex> {{---}} разбиение множества состояний ДКА,
 
*<tex>\mathtt{R}</tex> {{---}} класс состояний ДКА.
 
 
 
  '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector'''
 
    <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
 
    <tex>\mathtt{S} \leftarrow \varnothing </tex>
 
    '''for''' <tex>c \in \Sigma</tex>
 
      push <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{S}</tex>
 
    '''while''' <tex>\mathtt{S} \ne \varnothing</tex>
 
      <tex>\langle C,\ a \rangle</tex> <tex>\leftarrow</tex> pop '''from''' <tex>\mathtt{S}</tex>
 
      '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex>
 
        <tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C,\ a)</tex>
 
        '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex>
 
        replace <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> with' <tex>R_1</tex> '''and''' <tex>R_2</tex>
 
        '''if''' <tex>\langle R,\ c \rangle</tex> '''in''' <tex> \mathtt{S}</tex> <font color=darkgreen>// смотрим, есть ли пара <tex>\langle R,\ c \rangle</tex> в очереди </font>
 
          remove <tex>\langle R, c \rangle</tex> '''from''' <tex>\mathtt{S}</tex> <font color=darkgreen>// заменяем её на пары <tex>\langle R_1, c \rangle</tex>, <tex>\langle R_2, c \rangle</tex> если пара есть </font>
 
          push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
          push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
        '''else'''
 
            '''if''' <tex> |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| </tex> <font color=darkgreen>// вставляем любую иначе</font>
 
              push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
            '''else'''
 
              push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
    '''return''' <tex>\mathtt{P}</tex>
 
  
 
+
===Реализация===
       
+
<tex>Q</tex> {{---}} множество состояний ДКА.
 +
<tex>F</tex> {{---}} множество терминальных состояний.
 +
<tex>S</tex> {{---}} очередь из пар <tex>(C, a)</tex>.
 +
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
 +
<tex>R</tex> {{---}} класс состояний ДКА.
  
Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за <tex>T'</tex> (его нужно будет эффективно находить для каждой пары <tex>\langle C,\ a \rangle</tex>).
+
  <tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
 +
  <tex>\mathtt{S} \leftarrow \varnothing </tex>
 +
  '''for''' <tex>c \in \Sigma</tex>
 +
    '''insert''' <tex> (\mathtt{min} (\mathtt{F, Q} \setminus \mathtt{F}), c)</tex> '''in''' <tex>\mathtt{S}</tex>
 +
  '''while''' <tex>\mathtt{S} \ne \varnothing</tex>
 +
    <tex>(C, a) \leftarrow</tex> '''pop'''(<tex>\mathtt{S}</tex>)
 +
    <tex>T \leftarrow \{R \ | \ R \in \mathtt{P}, \ R</tex> splits by <tex>(C, a) \}</tex>
 +
    '''for each''' <tex>R</tex> '''in''' <tex>T</tex>
 +
      <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>) 
 +
      '''replace''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
 +
      '''for''' <tex>c \in \Sigma</tex>
 +
        '''if''' <tex>(R, c)</tex> '''in''' <tex>\mathtt{S}</tex>
 +
          '''replace''' <tex> (R, c)</tex> '''in''' <tex>\mathtt{S}</tex> '''with''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)</tex>
 +
        '''else'''
 +
          '''insert''' <tex>(\mathtt{min}(R_1, R_2), c)</tex> '''in''' <tex>\mathtt{S}</tex>
  
  '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector'''
+
К сожалению, совсем не очевидно, как быстро находить множество <tex>T</tex>. С другой стороны, понятно, что <tex>T \subset T'</tex>, где <tex>T'</tex> {{---}} это множество классов текущего разбиения, из состояний которых в автомате существует переход в состояния сплиттера <tex>C</tex> по символу <tex>a</tex>.
    <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
 
    <tex>\mathtt{S} \leftarrow \varnothing </tex>
 
    '''for''' <tex>c \in \Sigma</tex>
 
      push <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{S}</tex>
 
    '''while''' <tex>\mathtt{S} \ne \varnothing</tex>
 
      <tex>\langle C,\ a \rangle</tex> <tex>\leftarrow</tex> pop '''from''' <tex>\mathtt{S}</tex>
 
      <tex>\mathtt{Inverse} \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}</tex>
 
      <tex>T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}</tex> <font color=darkgreen>// находим классы, из состояний которых есть ребро в состояния сплиттера </font>
 
      '''for''' <tex>R</tex> '''in''' <tex>T'</tex> <font color=darkgreen>// перебираем только классы входящие в <tex>T'</tex></font>
 
        <tex> R_1, R_2 \leftarrow </tex> <tex>\mathtt{split}(R,\ C,\ a)</tex>
 
        '''if''' <tex> R_1 \ne \varnothing </tex> '''and''' <tex> R_2 \ne \varnothing </tex>
 
        replace <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> with <tex>R_1</tex> '''and''' <tex>R_2</tex>
 
        '''if''' <tex>\langle R,\ c \rangle</tex> '''in''' <tex> \mathtt{S}</tex>
 
          remove <tex>\langle R, c \rangle</tex> '''from''' <tex>\mathtt{S}</tex>
 
          push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
          push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
        '''else'''
 
            '''if''' <tex> |\mathtt{P}[R_1]| \leqslant |\mathtt{P}[R_2]| </tex>
 
              push <tex>\langle R_1, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
            '''else'''
 
              push <tex>\langle R_2, c \rangle</tex> '''into''' <tex>\mathtt{S}</tex>
 
    '''return''' <tex>\mathtt{P}</tex>
 
  
 +
Модифицируем наш алгоритм: для каждой очередной пары <tex> (C, a) </tex> будем находить <tex> T' </tex>, и с каждым классом состояний из <tex> T' </tex> будем производить те же действия, что и раньше.
  
Каждая итерация цикла <tex> \mathrm{while} </tex> может быть выполнена за <tex> O(|Q| + |\mathtt{Inverse}|)\,</tex> для текущей пары <tex>\langle C,\ a \rangle</tex>. Покажем, как можно достичь этой оценки.
+
  <tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>
 +
  <tex>\mathtt{S} \leftarrow \varnothing </tex>
 +
  '''for''' <tex>c \in \Sigma</tex>
 +
    '''insert''' <tex>(\mathtt{min} (\mathtt{F, Q} \setminus \mathtt{F}), c)</tex> '''in''' <tex>\mathtt{S}</tex>
 +
  '''while''' <tex>\mathtt{S} \ne \varnothing</tex>
 +
    <tex>(C, a) \leftarrow</tex> '''pop'''(<tex>\mathtt{S}</tex>)
 +
    <tex>\mathtt{Inverse} \leftarrow \{r \ | \ r \in \mathtt{Q}, \ \delta(r, a) \in C\}</tex>
 +
    <tex>T' \leftarrow \{R \ | \ R \in \mathtt{P}, \ R \cap \mathtt{Inverse} \neq \varnothing\}</tex>
 +
    '''for each''' <tex>R</tex> '''in''' <tex>T'</tex>
 +
      '''if''' <tex>R</tex> splits by <tex>(C, a)</tex>
 +
        <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>R, C, a</tex>) 
 +
        '''replace''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
 +
        '''for''' <tex>c \in \Sigma</tex>
 +
          '''if''' <tex>(R, c)</tex> '''in''' <tex>\mathtt{S}</tex>
 +
            '''replace''' <tex>(R, c)</tex> '''in''' <tex>S</tex> '''with''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)</tex>
 +
          '''else'''
 +
            '''insert''' <tex>(\mathtt{min}(R_1, R_2), c)</tex> '''in''' <tex>\mathtt{S}</tex>
  
Классы разбиения <tex>P</tex> будем поддерживать с помощью множеств на [[Хеш-таблица | хэш-таблицах]] (само же разбиение {{---}} обычный вектор, индекс {{---}} номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за <tex>O(1)</tex>).
+
Каждая итерация цикла <tex> while </tex> не может быть выполнена быстрее, чем за <tex> O(|\mathtt{Inverse}|) </tex> для текущей пары <tex> (C,a)</tex>. Покажем, как достичь этой оценки.
  
*<tex>\mathtt{Class}[r]</tex> {{---}} номер класса, которому принадлежит состояние <tex>r</tex>,
+
Разбиение <tex> P </tex> можно поддерживать четырьмя массивами:
*<tex>\mathtt{Queue}</tex> {{---}} очередь пар <tex>\langle C,\ a \rangle</tex>, где <tex>C</tex> {{---}} номер класса (сплиттера),
+
*<tex>\mathtt{Class}[r]</tex> {{---}} номер класса, которому принадлежит состояние <tex>r</tex>;
*<tex>\mathtt{Inv}[r][a]</tex> {{---}} массив состояний, из которых есть ребра по символу <tex>a</tex> в состояние <tex>r</tex> (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма).
+
*<tex>\mathtt{Part}[i]</tex> {{---}} указатель на голову [[Список#Двусвязный список|двусвязного списка]], содержащего состояния, принадлежащие классу <tex> i </tex>;
 +
*<tex>\mathtt{Card}[i]</tex> {{---}} количество состояний в классе <tex>i</tex>;
 +
*<tex>\mathtt{Place}[r]</tex> {{---}} указатель на состояние <tex>r</tex> в списке <tex>\mathtt{Part[Class}[r]]</tex>.
  
Для обработки <tex>T'</tex> за <tex>O(|Q| + |\mathtt{Inverse}|)\,</tex> нам понадобится следующая структура:
+
Так как мы храним указатель, где находится состояние в двусвязном списке, то операцию перемещения состояния из одного класса в другой можно выполнить за <tex>O(1)</tex>.
*<tex>\mathtt{Involved}</tex> {{---}} список из номеров классов, содержащихся во множестве <tex>T'</tex>,
 
*<tex>\mathtt{Count}</tex> {{---}} целочисленный массив, где <tex>\mathtt{Count}[i]</tex> хранит количество состояний из класса <tex>i</tex>, которые содержатся в <tex>\mathtt{Inverse}</tex>,
 
*<tex>\mathtt{Twin}</tex> {{---}} массив, хранящий в <tex>\mathtt{Twin}[i]</tex> номер нового класса, образовавшегося при разбиении класса <tex>i</tex>.
 
  
  '''function''' findEquivalenceClasses<tex>(Q,\ F,\ \delta)</tex>: '''vector'''
+
Чтобы эффективно находить множество <tex>\mathtt{Inverse}</tex>, построим массив <tex>\mathtt{Inv}</tex>, который для состояния <tex>r</tex> и символа <tex>a</tex> в <tex>\mathtt{Inv}[r][a]</tex> хранит множество состояний, из которых существует переход в <tex>r</tex> по символу <tex>a</tex>. Так как наш алгоритм не меняет изначальный автомат, то массив <tex>\mathtt{Inv}</tex> можно построить перед началом основной части алгоритма, что займет <tex>O(|\Sigma| |Q|)</tex> времени.
    <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
 
    '''for''' <tex>c \in \Sigma</tex>
 
      push <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{Queue}</tex>
 
    '''while''' <tex>\mathtt{Queue} \ne \varnothing</tex>
 
      <tex>\langle C,\ a \rangle</tex> <tex>\leftarrow</tex> pop '''from''' <tex>\mathtt{Queue}</tex>
 
      <tex>\mathtt{Involved} \leftarrow \varnothing</tex>
 
      '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
 
        <tex>i = \mathtt{Class}[r]</tex>
 
        '''if''' <tex>\mathtt{Count}[i] == 0</tex>
 
          insert <tex>i</tex> '''into''' <tex>\mathtt{Involved}</tex>
 
        <tex>\mathtt{Count}[i]++</tex>
 
      '''for''' <tex> i \in \mathtt{Involved}</tex>
 
        '''if''' <tex>\mathtt{Count}[i] < |\mathtt{P}[i]|</tex>
 
            insert <tex>\{\}</tex> '''into''' <tex>\mathtt{P}</tex> <font color=darkgreen>// создадим пустой класс в разбиении <tex>\mathtt{P}</tex></font>
 
            <tex>\mathtt{Twin}[i] = |\mathtt{P}|</tex> <font color=darkgreen> //запишем в <tex>\mathtt{Twin[i]}</tex> индекс нового класса</font>
 
      '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
 
        <tex>i = \mathtt{Class}[r]</tex>
 
        <tex>j = \mathtt{Twin}[i]</tex>
 
        '''if''' <tex>j \neq 0</tex>
 
            remove <tex>r</tex> '''from''' <tex>\mathtt{P}[i]</tex>
 
            add <tex>r</tex> '''to''' <tex>\mathtt{P}[j]</tex>
 
      '''for''' <tex> i \in \mathtt{Involved}</tex>
 
        <tex>j = \mathtt{Twin}[i]</tex>
 
        '''if''' <tex> j \neq 0 </tex>
 
          '''if''' <tex>|\mathtt{P}[j]| > |\mathtt{P}[i]|</tex>  <font color=darkgreen>// парный класс должен быть меньшего размера</font>
 
            <tex>\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])</tex> <font color=darkgreen>// swap за <tex>\mathtt{O(1)}</tex> {{---}} просто переставить указатели</font>
 
          '''for''' <tex>r \in \mathtt{P}[j]</tex> <font color=darkgreen> // обновляем номера классов для вершин, у которых они изменились</font>
 
            <tex>\mathtt{Class}[r] = j</tex>
 
          '''for''' <tex>c \in \Sigma</tex>
 
            push <tex>\langle j, c \rangle</tex> '''to''' <tex>\mathtt{Queue}</tex>
 
        <tex>\mathtt{Count}[i] = 0</tex>
 
        <tex>\mathtt{Twin}[i] = 0</tex>
 
    '''return''' <tex>\mathtt{P}</tex>
 
  
 +
Теперь научимся за <tex>O(|\mathtt{Inverse}|)</tex> обрабатывать множество <tex>T'</tex> и разбивать классы. Для этого нам понадобится следующая структура:
 +
*<tex>\mathtt{Counter}</tex> {{---}} количество классов;
 +
*<tex>\mathtt{Involved}</tex> {{---}} список из номеров классов, содержащихся во множестве <tex>T'</tex>;
 +
*<tex>\mathtt{Size}</tex> {{---}} целочисленный массив, где <tex>\mathtt{Size}[i]</tex> хранит количество состояний из класса <tex>i</tex>, которые содержатся в <tex>\mathtt{Inverse}</tex>;
 +
*<tex>\mathtt{Twin}</tex> {{---}} массив, хранящий в <tex>\mathtt{Twin}[i]</tex> номер нового класса, образовавшегося при разбиении класса <tex>i</tex>.
  
Стоит отметить, что массивы <tex>\mathtt{Count},\ \mathtt{Twin}\,</tex> аллоцируются ровно один раз при инициализации алгоритма.
+
Сам же алгоритм обработки <tex>T'</tex> будет выглядеть так:
 +
  <tex>\mathtt{Involved} \leftarrow \varnothing</tex>
 +
  '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
 +
    <tex>i = \mathtt{Class}[r]</tex>
 +
    '''if''' <tex>\mathtt{Size}[i] == 0</tex>
 +
      '''insert''' <tex>i</tex> '''in''' <tex>\mathtt{Involved}</tex>
 +
    <tex>\mathtt{Size}[i]++</tex>
 +
  '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
 +
    <tex>i = \mathtt{Class}[r]</tex>
 +
    '''if''' <tex>\mathtt{Size}[i] < \mathtt{Card}[i]</tex>
 +
      '''if''' <tex>\mathtt{Twin}[i] == 0</tex>
 +
        <tex>\mathtt{Counter}++</tex>
 +
        <tex>\mathtt{Twin[i]} = \mathtt{Counter}</tex>
 +
      '''move''' <tex>r</tex> '''from''' <tex>i</tex> '''to''' <tex>\mathtt{Twin}[i]</tex>
 +
  '''for''' <tex> j \in \mathtt{Involved}</tex>
 +
    <tex>\mathtt{Size}[j] = 0</tex>
 +
    <tex>\mathtt{Twin}[j] = 0</tex>
  
Также стоит отметить, что собственно наличие/отсутствие пары в очереди можно не проверять. Если для некоторого <tex>c</tex> пара <tex>\langle i, c \rangle</tex> уже была в очереди, то мы добавим её "вторую половинку" <tex>\langle \mathtt{Twin}[i], c \rangle</tex>. Если её в очереди не было, то мы вольны сами выбирать, какой подкласс добавлять в очередь, и таким образом добавляем опять же <tex>\langle \mathtt{Twin}[i], c \rangle</tex>.
+
Для быстрой проверки, находится ли пара <tex>(C,a)</tex> в очереди <tex>S</tex>, будем использовать массив <tex>\mathtt{InQueue}</tex> размера <tex>|Q| \times |\Sigma|</tex>, где <tex>\mathtt{InQueue}[C][a] = true</tex>, если пара <tex>(C,a)</tex> содержится в очереди.  
Кроме того, вместо очереди можно использовать вообще произвольную структуру, хранящую элементы, в том числе стэк, множество, так как порядок извлечения нам по сути не важен.
+
Так как при разбиении очередного класса <tex>R</tex> на подклассы <tex>R_1</tex> и <tex>R_2</tex> мы в действительности создаем лишь один новый класс, то замена класса <tex>R</tex> в очереди на подклассы, образовавшиеся при разбиении, сводится лишь к взаимодействию с массивом <tex>\mathtt{InQueue}</tex>. В результате каждая операция с очередью требует <tex>O(1)</tex> времени.
  
 
===Время работы===
 
===Время работы===
Строка 215: Строка 178:
 
|id = Лемма2
 
|id = Лемма2
 
|statement =  
 
|statement =  
Количество итераций цикла <tex>\mathrm{while}</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>.
+
Количество итераций цикла <tex>while</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>.
 
|proof =
 
|proof =
Для доказательства этого утверждения достаточно показать, что количество пар <tex>\langle C,\ a \rangle</tex> добавленных в очередь <tex>S</tex> не превосходит <tex> 2 |\Sigma| |Q| </tex>, так как на каждой итерации мы извлекаем одну пару из очереди.
+
Для доказательства этого утверждения достаточно показать, что количество пар <tex>(C,a)</tex> добавленных в очередь <tex>S</tex> не превосходит <tex> 2 |\Sigma| |Q| </tex>, так как на каждой итерации мы извлекаем одну пару из очереди.
  
По [[#Лемма1 | лемме(1)]] количество классов не превосходит <tex>2 |Q| - 1</tex>. Пусть <tex>C</tex> элемент текущего разбиения. Тогда количество пар <tex>\langle C,\ a \rangle</tex>, <tex>\ a \in \Sigma</tex> не может быть больше <tex>|\Sigma|</tex>. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит <tex> 2 |\Sigma| |Q| </tex>.
+
По [[#Лемма1 | лемме(1)]] количество классов не превосходит <tex>2 |Q| - 1</tex>. Пусть <tex>C</tex> элемент текущего разбиения. Тогда количество пар <tex>(C,a), \ a \in \Sigma</tex> не может быть больше <tex>|\Sigma|</tex>. Отсюда следует, что всего различных пар, которые можно добавить в очередь, не превосходит <tex> 2 |\Sigma| |Q| </tex>.
 
}}
 
}}
  
Строка 226: Строка 189:
 
|id = Лемма3
 
|id = Лемма3
 
|statement =  
 
|statement =  
Пусть <tex>a \in \Sigma</tex> и <tex>p \in Q</tex>. Тогда количество пар <tex>\langle C,\ a \rangle</tex>, где <tex>p \in C</tex>, которые мы удалим из очереди, не превосходит <tex>\log_2(|Q|)</tex> для фиксированных <tex>a</tex> и <tex>p</tex>.
+
Пусть <tex>a \in \Sigma</tex> и <tex>p \in Q</tex>. Тогда количество пар <tex>(C,a)</tex>, где <tex>p \in C</tex>, которые мы удалим из очереди, не превосходит <tex>\log_2(|Q|)</tex> для фиксированных <tex>a</tex> и <tex>p</tex>.
 
|proof =
 
|proof =
Рассмотрим пару <tex>\langle C,\ a \rangle</tex>, где <tex>p \in C</tex>, которую мы удаляем из очереди. И пусть <tex>\langle C',a \rangle</tex> следующая пара, где <tex>p \in C'</tex> и которую мы удалим из очереди. Согласно нашему алгоритму класс <tex>C'</tex> мог появиться в очереди только после операции <tex>\mathtt{replace}</tex>. Но после первого же разбиения класса <tex>C</tex> на подклассы мы добавим в очередь пару <tex>\langle C'', a \rangle</tex>, где <tex>C''</tex> меньший из образовавшихся подклассов, то есть <tex>|C''| \leqslant |C| \ / \ 2</tex>. Так же заметим, что <tex>C' \subseteq C''</tex>, а следовательно <tex>|C'| \leqslant |C| \ / \ 2</tex>. Но тогда таких пар не может быть больше, чем <tex>\log_2(|Q|)</tex>.  
+
Рассмотрим пару <tex>(C,a)</tex>, где <tex>p \in C</tex>, которую мы удаляем из очереди. И пусть <tex>(C',a)</tex> следующая пара, где <tex>p \in C'</tex> и которую мы удалим из очереди. Согласно нашему алгоритму класс <tex>C'</tex> мог появиться в очереди только после операции <tex>\mathtt{replace}</tex>. Но после первого же разбиения класса <tex>C</tex> на подклассы мы добавим в очередь пару <tex>(C'', a)</tex>, где <tex>C''</tex> меньший из образовавшихся подклассов, то есть <tex>|C''| \leqslant |C| \ / \ 2</tex>. Так же заметим, что <tex>C' \subseteq C''</tex>, а следовательно <tex>|C'| \leqslant |C| \ / \ 2</tex>. Но тогда таких пар не может быть больше, чем <tex>\log_2(|Q|)</tex>.  
 
}}
 
}}
  
Строка 235: Строка 198:
 
|id = Лемма4
 
|id = Лемма4
 
|statement =  
 
|statement =  
<tex>\sum |\mathtt{Inverse}|</tex> по всем итерациям цикла <tex>\mathrm{while}</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>.
+
<tex>\sum |\mathtt{Inverse}|</tex> по всем итерациям цикла <tex>while</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>.
 
|proof =
 
|proof =
Пусть <tex>x, y \in Q</tex>, <tex>a \in \Sigma</tex> и <tex> \delta(x, a) = y</tex>. Зафиксируем эту тройку. Заметим, что количество раз, которое <tex>x</tex> встречается в <tex>\mathtt{Inverse}\,</tex> при условии, что <tex> \delta(x, a) = y</tex>, совпадает с числом удаленных из очереди пар <tex>\langle C,\ a \rangle</tex>, где <tex>y \in C</tex>. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит <tex>\log_2(|Q|)</tex>. Просуммировав по всем <tex> x \in Q </tex> и по всем <tex> a \in \Sigma</tex> мы получим утверждение леммы.
+
Пусть <tex>x, y \in Q</tex>, <tex>a \in \Sigma</tex> и <tex> \delta(x, a) = y</tex>. Зафиксируем эту тройку. Заметим, что количество раз, которое <tex>x</tex> встречается в <tex>\mathtt{Inverse}</tex> при условии, что <tex> \delta(x, a) = y</tex>, совпадает с числом удаленных из очереди пар <tex>(C, a)</tex>, где <tex>y \in C</tex>. Но по [[#Лемма3 | лемме(3)]] эта величина не превосходит <tex>\log_2(|Q|)</tex>. Просуммировав по всем <tex> x \in Q </tex> и по всем <tex> a \in \Sigma</tex> мы получим утверждение леммы.
 
}}
 
}}
  
Строка 248: Строка 211:
 
*Построение массива <tex>\mathtt{Inv}</tex> занимает <tex>O(|\Sigma| |Q|)</tex> времени.  
 
*Построение массива <tex>\mathtt{Inv}</tex> занимает <tex>O(|\Sigma| |Q|)</tex> времени.  
  
*По [[#Лемма2 | второй лемме]] количество итераций цикла <tex>\mathrm{while}</tex> не превосходит <tex>O(|\Sigma| |Q|)</tex>.
+
*По [[#Лемма2 | второй лемме]] количество итераций цикла <tex>while</tex> не превосходит <tex>O(|\Sigma| |Q|)</tex>.
  
*Операции с множеством <tex>T'</tex> и разбиение классов на подклассы требуют <tex>O(\sum(|\mathtt{Inverse}|))\,</tex> времени. Но по [[#Лемма4 | лемме(4)]] <tex>\sum(|\mathtt{Inverse}|)\,</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>, то есть данная часть алгоритма выполняется за <tex>O(|\Sigma| |Q| \log_2(|Q|))</tex>.
+
*Операции с множеством <tex>T'</tex> и разбиение классов на подклассы требуют <tex>O(\sum(|\mathtt{Inverse}|))</tex> времени. Но по [[#Лемма4 | лемме(4)]] <tex>\sum(|\mathtt{Inverse}|)</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>, то есть данная часть алгоритма выполняется за <tex>O(|\Sigma| |Q| \log_2(|Q|))</tex>.
  
 
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем <tex>2 |Q| - 1</tex> классов, из чего следует, что количество операций <tex>\mathtt{replace}</tex> равно <tex>O(|\Sigma| |Q|)</tex>.
 
*В [[#Лемма1 | лемме(1)]] мы показали, что в процессе работы алгоритма не может появится больше, чем <tex>2 |Q| - 1</tex> классов, из чего следует, что количество операций <tex>\mathtt{replace}</tex> равно <tex>O(|\Sigma| |Q|)</tex>.
Строка 257: Строка 220:
 
}}
 
}}
  
=== Альтернативная реализация ===
+
== Литература ==
Вообще, алгоритм можно реализовать и с меньшим количеством используемых структур (что делает код на порядок читабельнее).
 
 
 
Все классы разбиения будем по-прежнему хранить в векторе хэш-сетов <tex>\mathtt{P}</tex>.
 
 
 
*<tex>\mathtt{Class}[r]</tex> {{---}} индекс класса в <tex>\mathtt{P}</tex>, которому принадлежит состояние <tex>r</tex>,
 
*<tex>\mathtt{Queue}</tex> {{---}} очередь из пар <tex>\langle C,\ a \rangle</tex>,
 
*<tex>\mathtt{Inv}[r][a]</tex> {{---}} массив состояний, из которых есть ребра по символу <tex>a</tex> в состояние <tex>r</tex> (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма),
 
*<tex>\mathtt{Involved}</tex> {{---}} ассоциативный массив из номеров классов в векторы из номеров вершин.
 
 
 
  <tex>\mathtt{findEquivalenceClasses}(Q,\ F,\ \delta)</tex>:
 
    <tex>\mathtt{P} \leftarrow \{ F, \ Q \setminus F \}</tex>
 
    '''for''' <tex>c \in \Sigma</tex>
 
      insert <tex>\langle F,\ c \rangle</tex>, <tex>\langle Q \setminus F,\ c \rangle</tex> '''into''' <tex> \mathtt{Queue}</tex>
 
    '''while''' <tex>\mathtt{Queue} \ne \varnothing</tex>
 
      <tex>\langle C,\ a \rangle</tex> <tex>\leftarrow</tex> pop '''from''' <tex>\mathtt{Queue}</tex>
 
      <tex>\mathtt{Involved} = \{\}</tex>
 
      '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex>
 
        <tex>i = \mathtt{Class}[r]</tex>
 
        '''if''' <tex>\mathtt{Involved}[i] == \varnothing</tex>
 
            <tex>\mathtt{Involved}[i] = \{\}</tex>
 
        add <tex>r</tex> '''to''' <tex>\mathtt{Involved}[i]</tex>
 
      '''for''' <tex> i \in \mathtt{Involved}</tex> <font color=darkgreen>//Перебираем ключи <tex>\mathtt{Involved}</tex></font>
 
        '''if''' <tex>|\mathtt{Involved}[i]| < |\mathtt{P}[i]|</tex>
 
            '''insert''' <tex>\{\}</tex> '''into''' <tex>\mathtt{P}</tex> <font color=darkgreen>//Создадим пустой класс в разбиении <tex>\mathtt{P}</tex></font>
 
            <tex>j = |\mathtt{P}|</tex> <font color=darkgreen>//Запишем в <tex>j</tex> индекс нового класса</font>
 
            '''for''' <tex>r</tex> '''in''' <tex>\mathtt{Involved}[i]</tex>
 
              remove <tex>r</tex> '''from''' <tex>\mathtt{P}[i]</tex>
 
              add <tex>r</tex> '''to''' <tex>\mathtt{P}[j]</tex>
 
            '''if''' <tex>|\mathtt{P}[j]| > |\mathtt{P}[i]|</tex>  <font color=darkgreen>//Парный класс должен быть меньшего размера</font>
 
                <tex>\mathtt{swap}(\mathtt{P}[i],\ \mathtt{P}[j])</tex> <font color=darkgreen>//swap за <tex>\mathtt{O(1)}</tex> {{---}} просто переставить указатели</font>
 
            '''for''' <tex>r \in \mathtt{P}[j]</tex> <font color=darkgreen>//Обновляем номера классов для вершин, у которых они изменились</font>
 
              <tex>\mathtt{Class}[r] = j</tex>
 
            '''for''' <tex>c \in \Sigma</tex>
 
              push <tex>\langle j, c \rangle</tex> '''into''' <tex>\mathtt{Queue}</tex>
 
    '''return''' <tex>\mathtt{P}</tex>
 
 
 
== См. также ==
 
 
 
* [[Алгоритм Бржозовского]]
 
 
 
== Источники информации ==
 
 
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
 
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
 
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.
 
* ''D. Gries.'' Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.
 
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.
 
* ''Hang Zhou.'' Implementation of Hopcroft's Algorithm, 19 December 2009.
* [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf ''John Hopcroft'' An O(nlogn) algorithm for minimizing states in a finite automation]
 
 
  
 
[[Категория: Теория формальных языков]]
 
[[Категория: Теория формальных языков]]
 
[[Категория: Автоматы и регулярные языки]]
 
[[Категория: Автоматы и регулярные языки]]

Пожалуйста, учтите, что любой ваш вклад в проект «Викиконспекты» может быть отредактирован или удалён другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Викиконспекты:Авторские права). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!

Чтобы изменить эту страницу, пожалуйста, ответьте на приведённый ниже вопрос (подробнее):

Отменить | Справка по редактированию (в новом окне)