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

Материал из Викиконспекты
Перейти к: навигация, поиск
(Псевдокод)
Строка 28: Строка 28:
 
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
 
<tex>P</tex> {{---}} разбиение множества состояний ДКА.
 
<tex>R</tex> {{---}} класс состояний ДКА.
 
<tex>R</tex> {{---}} класс состояний ДКА.
  <tex>W \leftarrow \{ F, Q \setminus F \}</tex>
 
 
   <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
 
   <tex>P \leftarrow \{ F, Q \setminus F \}</tex>
 +
  <tex>W \leftarrow \{ \}</tex>
 +
  for all <tex>a \in \Sigma</tex>
 +
    <tex>W</tex>.push(<tex>F, a</tex>)
 +
    <tex>W</tex>.push(<tex>Q \setminus F, a</tex>)
 
   while not <tex>W</tex>.isEmpty()  
 
   while not <tex>W</tex>.isEmpty()  
     <tex>W</tex>.pop(<tex>S</tex>)
+
     <tex>W</tex>.pop(<tex>S, a</tex>)
     for all <tex>a \in \Sigma</tex>
+
     for all <tex>R</tex> in <tex>P</tex>  
      for all <tex>R</tex> in <tex>P</tex>  
+
      <tex>R_1 = R \cap \delta^{-1} (S, a) </tex>
        <tex>R_1 = R \cap \delta^{-1} (S, a) </tex>
+
      <tex>R_2 = R \setminus R_1</tex>
        <tex>R_2 = R \setminus R_1</tex>
+
      if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex>
        if <tex> |R_1| \ne 0</tex> and <tex>|R_2| \ne 0</tex>
+
        replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex>
          replace <tex>R</tex> in <tex>P</tex> with <tex>R_1</tex> and <tex>R_2</tex>
+
        <tex>W</tex>.push(<tex>R_1</tex>)
          <tex>W</tex>.push(<tex>R_1</tex>)
+
        <tex>W</tex>.push(<tex>R_2</tex>)
          <tex>W</tex>.push(<tex>R_2</tex>)
 
 
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
 
Когда очередь станет пустой будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить.
  

Версия 17:54, 15 января 2013

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

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

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

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

Определение:
Класс [math]S[/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 S[/math]
  2. [math]\forall r \in R_2 \,\,\, \delta(r, a) \notin S[/math]

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

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

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

Псевдокод

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

 [math]P \leftarrow \{ F, Q \setminus F \}[/math]
 [math]W \leftarrow \{ \}[/math]
 for all [math]a \in \Sigma[/math]
   [math]W[/math].push([math]F, a[/math])
   [math]W[/math].push([math]Q \setminus F, a[/math])
 while not [math]W[/math].isEmpty() 
   [math]W[/math].pop([math]S, a[/math])
   for all [math]R[/math] in [math]P[/math] 
     [math]R_1 = R \cap \delta^{-1} (S, a) [/math]
     [math]R_2 = R \setminus R_1[/math]
     if [math] |R_1| \ne 0[/math] and [math]|R_2| \ne 0[/math]
       replace [math]R[/math] in [math]P[/math] with [math]R_1[/math] and [math]R_2[/math]
       [math]W[/math].push([math]R_1[/math])
       [math]W[/math].push([math]R_2[/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]W[/math] — очередь. [math]P[/math] — разбиение множества состояний ДКА. [math]R[/math] — класс состояний ДКА.

 if [math] |F| \le |Q \setminus F|[/math]
     [math]W[/math].push([math]F[/math])
   else
     [math]W[/math].push([math]Q \setminus F[/math])
 [math]P \leftarrow \{ F, Q \setminus F \}[/math]
 while not [math]W[/math].isEmpty() 
   [math]W[/math].pop([math]S[/math])
   for all [math]a \in \Sigma[/math]
     for each [math]R[/math] in [math]P[/math] split by [math]S[/math]  
       replace [math]R[/math] in [math]P[/math] with [math]R_1[/math] and [math]R_2[/math]
       if [math]R[/math] in [math]W[/math]
           replace [math]R[/math] in [math]W[/math] with [math]R_1[/math] and [math]R_2[/math]
         else
           if [math] |R_1| \le |R_2|[/math]
               [math]W[/math].push([math]R_1[/math])
             else
               [math]W[/math].push([math]R_2[/math])

Время работы алгоритма

Время работы алгоритма равно [math]O(|\Sigma| * n\log{n})[/math], где [math] n [/math] — количество состояний ДКА, а [math] \Sigma [/math]— алфавит. Это следует из того, что каждое из ребер, а их порядка [math] |\Sigma| * n [/math], участвует не более чем в [math] \log{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.