Алгоритм Хопкрофта — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Псевдокод)
(Алгоритм Хопкрофта)
Строка 85: Строка 85:
 
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
 
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
 
<tex>R</tex> {{---}} класс состояний ДКА.
 
<tex>R</tex> {{---}} класс состояний ДКА.
 +
 
   <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
 
   <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
 
   <tex>S \leftarrow \varnothing </tex>
 
   <tex>S \leftarrow \varnothing </tex>
 
   '''for''' <tex>c \in \Sigma</tex>
 
   '''for''' <tex>c \in \Sigma</tex>
     <tex> insert </tex> <tex>(min (F, Q \setminus F), c)</tex> '''to''' <tex>S</tex>
+
     <tex> insert \ (min (F, Q \setminus F), c)</tex> '''to''' <tex>S</tex>
 
   '''while''' <tex>S \ne \varnothing</tex>
 
   '''while''' <tex>S \ne \varnothing</tex>
     <tex>remove </tex> <tex>(C, a)</tex> '''from''' <tex>S</tex>
+
     <tex>(C, a) \leftarrow pop(S)</tex>
     '''for each''' <tex>R</tex> '''in''' <tex>P</tex> '''split by''' <tex>(C, a)</tex>
+
    <tex>T = \{R \ | \ R \in P, \ R</tex> '''split by''' <tex>(C, a) \}</tex>
 +
     '''for each''' <tex>R</tex> '''in''' <tex>T</tex>
 
       <tex> R_1, R_2 \leftarrow </tex> <tex> split(R, C, a) </tex>   
 
       <tex> R_1, R_2 \leftarrow </tex> <tex> split(R, C, a) </tex>   
 
       <tex>replace</tex> <tex>R</tex> '''in''' <tex>P</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
 
       <tex>replace</tex> <tex>R</tex> '''in''' <tex>P</tex> '''with''' <tex>R_1</tex> '''and''' <tex>R_2</tex>
 
       '''for''' <tex>c \in \Sigma</tex>
 
       '''for''' <tex>c \in \Sigma</tex>
 
         '''if''' <tex>(R, c)</tex> '''in''' <tex>S</tex>
 
         '''if''' <tex>(R, c)</tex> '''in''' <tex>S</tex>
           <tex>replace</tex> <tex>(R, c)</tex> '''in''' <tex>S</tex> '''with''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)</tex>
+
           <tex>replace \ (R, c)</tex> '''in''' <tex>S</tex> '''with''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)</tex>
 
         '''else'''
 
         '''else'''
           <tex>insert</tex> <tex>(min(R_1, R_2), c)</tex> '''to''' <tex>S</tex>
+
           <tex>insert \ (min(R_1, R_2), c)</tex> '''to''' <tex>S</tex>
 +
 
 +
К сожалению, совсем не очевидно, как быстро находить множество <tex>T</tex>. С другой стороны, понятно, что <tex>T</tex> {{---}} это подмножество классов текущего разбиения, из которых в ДКА существует переход в сплиттер <tex>C</tex> по символу <tex>a</tex>.
 +
 
 +
Пусть <tex>Inverse = \{r \ | \ r \in Q, \ \delta(r, a) \in C\}</tex>, а <tex>T' = \{R \ | \ R \in P, \ R \cap Inverse \neq \varnothing\}</tex>. Тогда <tex> T \subset T'</tex>.
 +
 
 +
Модифицируем наш алгоритм: для каждой очередной пары <tex> (C, a) </tex> будем находить <tex> T' </tex>, и с каждым классом состояний из <tex> T' </tex> будем производить те же действия, что и раньше.
 +
 
 +
  <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
 +
  <tex>S \leftarrow \varnothing </tex>
 +
  '''for''' <tex>c \in \Sigma</tex>
 +
    <tex> insert \ (min (F, Q \setminus F), c)</tex> '''to''' <tex>S</tex>
 +
  '''while''' <tex>S \ne \varnothing</tex>
 +
    <tex>(C, a) \leftarrow pop(S)</tex>
 +
    <tex>Inverse \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}</tex>
 +
    <tex>T' \leftarrow \{R \ | \ R \in P, \ R \cap Inverse \neq \varnothing\}</tex>
 +
    '''for each''' <tex>R</tex> '''in''' <tex>T'</tex>
 +
      '''if''' <tex>R</tex> '''split by''' <tex>(C, a)</tex>
 +
        <tex> R_1, R_2 \leftarrow </tex> <tex> split(R, C, a) </tex> 
 +
        <tex>replace</tex> <tex>R</tex> '''in''' <tex>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>S</tex>
 +
            <tex>replace \ (R, c)</tex> '''in''' <tex>S</tex> '''with''' <tex>(R_1, c)</tex> '''and''' <tex>(R_2, c)</tex>
 +
          '''else'''
 +
            <tex>insert \ (min(R_1, R_2), c)</tex> '''to''' <tex>S</tex>
  
 
===Время работы===
 
===Время работы===

Версия 21:41, 7 декабря 2013

Пусть дан автомат, распознающий определенный язык. Требуется найти эквивалентный автомат с наименьшим количеством состояний.

Минимизация ДКА

Если в ДКА существуют два эквивалентных состояния, то при их объединении мы получим эквивалентный ДКА, так как распознаваемый язык не изменится. Основная идея минимизации состоит в разбиении множества состояний на классы эквивалентности, полученные классы и будут состояниями минимизированного ДКА.

Простой алгоритм

Определение:
Класс [math]C[/math] разбивает класс [math]R[/math] по символу [math]a[/math] на [math]R_1[/math] и [math]R_2[/math], если
  1. [math]\forall r \in R_1 \,\,\, \delta(r, a) \in C[/math]
  2. [math]\forall r \in R_2 \,\,\, \delta(r, a) \notin C[/math]

Если класс [math]R[/math] может быть разбит по символу [math]a[/math], то он содержит хотя бы одну пару неэквивалентных состояний (так как существует строка которая их различает). Если класс нельзя разбить, то он состоит из эквивалентных состояний. Поэтому самый простой алгоритм состоит в том, чтобы разбивать классы текущего разбиения до тех пор пока это возможно.

Итеративно строим разбиение множества состояний следующим образом.

  1. Первоначальное разбиение множества состояний — класс допускающих состояний [math]F[/math] и класс недопускающих состояний [math]Q \setminus F[/math].
  2. Перебираются символы алфавита [math]c \in \Sigma[/math], все пары [math](F, c)[/math] и [math](Q \setminus F, c)[/math] помещаются в очередь.
  3. Из очереди извлекается пара [math](C, a)[/math], [math]C[/math] далее именуется как сплиттер.
  4. Все классы текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу [math]a[/math] переходят в сплиттер, а второй из всех оставшихся.
  5. Те классы, которые разбились на два непустых подкласса, заменяются этими подклассами в разбиении, а также добавляются в очередь.
  6. Пока очередь не пуста, выполняем п.3 – п.5.

Псевдокод

[math]Q[/math] — множество состояний ДКА. [math]F[/math] — множество терминальных состояний. [math]S[/math] — очередь пар [math](C, a)[/math]. [math]P[/math] — разбиение множества состояний ДКА. [math]R[/math] — класс состояний ДКА.

 [math]P \leftarrow \{ F, Q \setminus F \}[/math]
 [math]S \leftarrow \varnothing [/math]
 for [math]c \in \Sigma[/math]
   [math]insert[/math] [math](F, c)[/math] to [math]S[/math]
   [math]insert[/math] [math](Q \setminus F, c)[/math] to [math]S[/math]
 while [math] S \ne \varnothing [/math]
   [math]remove[/math] [math](C, a)[/math] from [math]S[/math]
   for [math]R[/math] in [math]P[/math] 
     [math]R_1 = R \cap \delta^{-1} (C, a) [/math]
     [math]R_2 = R \setminus R_1[/math]
     if [math] R_1 \ne \varnothing [/math] and [math] R_2 \ne \varnothing [/math]
       [math]replace[/math] [math]R[/math] in [math]P[/math] with [math]R_1[/math] and [math]R_2[/math]
       for  [math] c \in \Sigma [/math] 
         [math]insert[/math] [math](R_1, c)[/math] to [math]S[/math]
         [math]insert[/math] [math](R_2, c)[/math] to [math]S[/math]

Когда очередь [math]S[/math] станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.

Время работы

Время работы алгоритма оценивается как [math]O(|\Sigma| \cdot n^2)[/math], где [math] n [/math] — количество состояний ДКА, а [math] \Sigma [/math]— алфавит. Это следует из того, что если пара [math](C, a)[/math] попала в очередь, и класс [math]C[/math] использовался в качестве сплиттера, то при последующем разбиении этого класса в очередь добавляется два класса [math]C_1[/math] и [math]C_2[/math], причем можно гарантировать лишь следующее уменьшение размера: [math]|C| \ge |C_i| + 1[/math]. Каждое состояние изначально принадлежит лишь одному классу в очереди, поэтому каждый переход в автомате будет просмотрен не более, чем [math]O(n)[/math] раз. Учитывая, что ребер всего [math]O(|\Sigma| \cdot n)[/math], получаем указанную оценку.

Алгоритм Хопкрофта

Лемма:
Класс [math]R = R_1 \cup R_2[/math] и [math]R_1 \cap R_2 = \varnothing[/math], тогда разбиение всех классов (текущее разбиение) по символу [math]a[/math] любыми двумя классами из [math]R, R_1, R_2[/math] эквивалентно разбиению всех классов с помощью [math]R, R_1, R_2[/math] по символу [math]a[/math].
Доказательство:
[math]\triangleright[/math]

Разобьем все классы с помощью [math]R [/math] и [math] R_1[/math] по символу [math]a[/math], тогда для любого класса [math]B[/math] из текущего разбиения выполняется

[math]\forall r \in B \,\,\, \delta(r, a) \in R[/math] and [math] \delta(r, a) \in R_1[/math] or
[math]\forall r \in B \,\,\, \delta(r, a) \in R[/math] and [math] \delta(r, a) \notin R_1[/math] or
[math]\forall r \in B \,\,\, \delta(r, a) \notin R[/math] and [math] \delta(r, a) \notin R_1[/math]

А так как [math]R = R_1 \cup R_2[/math] и [math]R_1 \cap R_2 = \varnothing[/math] то выполняется

[math]\forall r \in B \,\,\, \delta(r, a) \in R_2 [/math] or
[math] \forall r \in B \,\,\, \delta(r, a) \notin R_2[/math]

Из этого следует, что разбиение всех классов с помощью [math]R_2[/math] никак не повлияет на текущее разбиение.
Аналогично доказывается и для разбиения с помощью [math]R [/math] и [math] R_2[/math] по символу [math]a[/math].
Разобьем все классы с помощью [math]R_1[/math] и [math] R_2[/math] по символу [math]a[/math], тогда для любого класса [math]B[/math] из текущего разбиения выполняется

[math]\forall r \in B \,\,\, \delta(r, a) \in R_1[/math] and [math] \delta(r, a) \notin R_2[/math] or
[math]\forall r \in B \,\,\, \delta(r, a) \notin R_1[/math] and [math] \delta(r, a) \in R_2[/math] or
[math]\forall r \in B \,\,\, \delta(r, a) \notin R_1[/math] and [math] \delta(r, a) \notin R_2[/math]

А так как [math]R = R_1 \cup R_2[/math] и [math]R_1 \cap R_2 = \varnothing[/math] то выполняется

[math]\forall r \in B \,\,\, \delta(r, a) \in R [/math] or
[math] \forall r \in B \,\,\, \delta(r, a) \notin R[/math]
Из этого следует, что разбиение всех классов с помощью [math]R[/math] никак не повлияет на текущее разбиение.
[math]\triangleleft[/math]

Алгоритм Хопкрофта отличается от простого тем, что иначе добавляет классы в очередь. Если класс [math]R[/math] уже есть в очереди, то согласно лемме можно просто заменить его на [math]R_1[/math] и [math]R_2[/math]. Если класса [math]R[/math] нет в очереди, то согласно лемме в очередь можно добавить класс [math]R[/math] и любой из [math]R_1[/math] и [math]R_2[/math], а так как для любого класса [math]B[/math] из текущего разбиения выполняется

[math]\forall r \in B \,\,\, \delta(r, a) \in R [/math] or
[math] \forall r \in B \,\,\, \delta(r, a) \notin R[/math]

то в очередь можно добавить только меньшее из [math]R_1[/math] и [math]R_2[/math].

Псевдокод

[math]Q[/math] — множество состояний ДКА. [math]F[/math] — множество терминальных состояний. [math]S[/math] — очередь из пар [math](C, a)[/math]. [math]P[/math] — разбиение множества состояний ДКА. [math]R[/math] — класс состояний ДКА.

 [math]P \leftarrow \{ F, Q \setminus F \}[/math]
 [math]S \leftarrow \varnothing [/math]
 for [math]c \in \Sigma[/math]
   [math] insert \ (min (F, Q \setminus F), c)[/math] to [math]S[/math]
 while [math]S \ne \varnothing[/math]
   [math](C, a) \leftarrow pop(S)[/math]
   [math]T = \{R \ | \ R \in P, \ R[/math] split by [math](C, a) \}[/math]
   for each [math]R[/math] in [math]T[/math]
     [math] R_1, R_2 \leftarrow [/math] [math] split(R, C, a) [/math]  
     [math]replace[/math] [math]R[/math] in [math]P[/math] with [math]R_1[/math] and [math]R_2[/math]
     for [math]c \in \Sigma[/math]
       if [math](R, c)[/math] in [math]S[/math]
         [math]replace \ (R, c)[/math] in [math]S[/math] with [math](R_1, c)[/math] and [math](R_2, c)[/math]
       else
         [math]insert \ (min(R_1, R_2), c)[/math] to [math]S[/math]

К сожалению, совсем не очевидно, как быстро находить множество [math]T[/math]. С другой стороны, понятно, что [math]T[/math] — это подмножество классов текущего разбиения, из которых в ДКА существует переход в сплиттер [math]C[/math] по символу [math]a[/math].

Пусть [math]Inverse = \{r \ | \ r \in Q, \ \delta(r, a) \in C\}[/math], а [math]T' = \{R \ | \ R \in P, \ R \cap Inverse \neq \varnothing\}[/math]. Тогда [math] T \subset T'[/math].

Модифицируем наш алгоритм: для каждой очередной пары [math] (C, a) [/math] будем находить [math] T' [/math], и с каждым классом состояний из [math] T' [/math] будем производить те же действия, что и раньше.

 [math]P \leftarrow \{ F, Q \setminus F \}[/math]
 [math]S \leftarrow \varnothing [/math]
 for [math]c \in \Sigma[/math]
   [math] insert \ (min (F, Q \setminus F), c)[/math] to [math]S[/math]
 while [math]S \ne \varnothing[/math]
   [math](C, a) \leftarrow pop(S)[/math]
   [math]Inverse \leftarrow \{r \ | \ r \in Q, \ \delta(r, a) \in C\}[/math]
   [math]T' \leftarrow \{R \ | \ R \in P, \ R \cap Inverse \neq \varnothing\}[/math]
   for each [math]R[/math] in [math]T'[/math]
     if [math]R[/math] split by [math](C, a)[/math]
       [math] R_1, R_2 \leftarrow [/math] [math] split(R, C, a) [/math]  
       [math]replace[/math] [math]R[/math] in [math]P[/math] with [math]R_1[/math] and [math]R_2[/math]
       for [math]c \in \Sigma[/math]
         if [math](R, c)[/math] in [math]S[/math]
           [math]replace \ (R, c)[/math] in [math]S[/math] with [math](R_1, c)[/math] and [math](R_2, c)[/math]
         else
           [math]insert \ (min(R_1, R_2), c)[/math] to [math]S[/math]

Время работы

Время работы модифицированного алгоритма оценивается как [math]O(|\Sigma| \cdot n\log{n})[/math], где [math] n [/math] — количество состояний ДКА, а [math] \Sigma [/math]— алфавит. В данном случае при последующем разбиении в очередь будет добавлен класс [math]S_1[/math], причем [math]|S| \ge 2|S_1|[/math]. Каждый переход в автомате будет просмотрен не более, чем [math]O(\log{n})[/math] раз, ребер всего [math]O(|\Sigma| \cdot n)[/math], отсюда указанная оценка.

Литература

  • Хопкрофт Д., Мотвани Р., Ульман Д. Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — М.: Издательский дом «Вильямс», 2002. — С. 177 — ISBN 5-8459-0261-4 (рус.)
  • D. Gries. Describing an algorithm by Hopcroft. Technical Report TR-72-151, Cornell University, December 1972.