Редактирование: Минимизация ДКА, алгоритм Хопкрофта (сложность O(n log n))
Внимание! Вы не авторизовались на сайте. Ваш IP-адрес будет публично видимым, если вы будете вносить любые правки. Если вы войдёте или создадите учётную запись, правки вместо этого будут связаны с вашим именем пользователя, а также у вас появятся другие преимущества.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия | Ваш текст | ||
Строка 15: | Строка 15: | ||
Итеративно строим разбиение множества состояний следующим образом. | Итеративно строим разбиение множества состояний следующим образом. | ||
− | # Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ F, | + | # Первоначальное разбиение множества состояний {{---}} класс допускающих состояний <tex>F</tex> и класс недопускающих состояний (<tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex>). |
− | # Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex> | + | # Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>(F, c)</tex> и <tex>(Q \setminus F, c)</tex> помещаются в очередь. |
− | # Из очереди извлекается пара <tex> | + | # Из очереди извлекается пара <tex>(C, a)</tex>, <tex>C</tex> далее именуется как сплиттер. |
− | # Каждый класс <tex>R</tex> текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер <tex> | + | # Каждый класс <tex>R</tex> текущего разбиения разбиваются на 2 подкласса (один из которых может быть пустым). Первый состоит из состояний, которые по символу <tex>a</tex> переходят в сплиттер (<tex>R_1</tex>), а второй из всех оставшихся (<tex>R_2</tex>). |
− | # Если <tex>R</tex> разбился на два непустых подкласса ( | + | # Если <tex>R</tex> разбился на два непустых подкласса (т.е. <tex> R_1 \ne \emptyset</tex> and <tex>R_2 \ne \emptyset </tex>). |
## В разбиении <tex>P</tex> класс <tex>R</tex> заменяется на свои подклассы <tex>R_1</tex> и <tex>R_2</tex>. | ## В разбиении <tex>P</tex> класс <tex>R</tex> заменяется на свои подклассы <tex>R_1</tex> и <tex>R_2</tex>. | ||
− | ## Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex> | + | ## Перебираются символы алфавита <tex>c \in \Sigma</tex>, все пары <tex>(R_1, c)</tex> и <tex>(R_2, c)</tex> помещаются в очередь. |
# Пока очередь не пуста, выполняем п.3 – п.5. | # Пока очередь не пуста, выполняем п.3 – п.5. | ||
===Псевдокод=== | ===Псевдокод=== | ||
− | + | <tex>Q</tex> {{---}} множество состояний ДКА. | |
− | + | <tex>F</tex> {{---}} множество терминальных состояний. | |
− | + | <tex>S</tex> {{---}} очередь пар <tex>(C, a)</tex>. | |
− | + | <tex>P</tex> {{---}} разбиение множества состояний ДКА. | |
− | + | <tex>R</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{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex> | ||
+ | '''insert''' <tex>(\mathtt{Q} \setminus \mathtt{F}, c)</tex> '''in''' <tex>\mathtt{S}</tex> | ||
+ | '''while''' <tex>\mathtt{S} \ne \varnothing</tex> | ||
+ | <tex>(C, a) \leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex> | ||
+ | '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> | ||
+ | <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>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> | ||
+ | '''for''' <tex>c \in \Sigma</tex> | ||
+ | '''insert''' <tex>(R_1, c)</tex> '''in''' <tex>\mathtt{S}</tex> | ||
+ | '''insert''' <tex>(R_2, c)</tex> '''in''' <tex>\mathtt{S}</tex> | ||
Когда очередь <tex>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить. | Когда очередь <tex>S</tex> станет пустой, будет получено разбиение на классы эквивалентности, так как больше ни один класс невозможно разбить. | ||
===Время работы=== | ===Время работы=== | ||
− | Время работы алгоритма оценивается как <tex>O(|\Sigma| \cdot n^2)</tex>, где <tex> n </tex> {{---}} количество состояний ДКА, а <tex> \Sigma </tex> {{---}} алфавит. Это следует из того, что если пара <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>, получаем указанную оценку. |
== Алгоритм Хопкрофта== | == Алгоритм Хопкрофта== | ||
Строка 60: | Строка 57: | ||
|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 | + | :<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 | + | :<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 | + | :<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 | + | :<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 | + | :<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 | + | :<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 | + | :<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 | + | :<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> никак не повлияет на текущее разбиение. | ||
}} | }} | ||
Строка 81: | Строка 78: | ||
После замены класса <tex>R</tex> в разбиении <tex>P</tex> на его подклассы <tex>R_1</tex> и <tex>R_2</tex>, как и раньше перебираем символы алфавита <tex>c \in \Sigma</tex>. | После замены класса <tex>R</tex> в разбиении <tex>P</tex> на его подклассы <tex>R_1</tex> и <tex>R_2</tex>, как и раньше перебираем символы алфавита <tex>c \in \Sigma</tex>. | ||
− | Если пара <tex> | + | Если пара <tex>(R, c)</tex> уже есть в очереди, то согласно лемме можно просто заменить её на пары <tex>(R_1, c)</tex> и <tex>(R_2, c)</tex>. |
− | Если пары <tex> | + | Если пары <tex>(R, c)</tex> нет в очереди, то достаточно добавить любую из пар <tex>(R_1, c)</tex> и <tex>(R_2, c)</tex>. Это следует из следующих соображений: <tex>R</tex> может быть в разбиении только если в очередь были положены пары <tex>(R, a)</tex> для <tex>\forall a \in \Sigma</tex>, а поскольку в очереди пары <tex>(R, c)</tex> нет, то мы её уже успели рассмотреть, следовательно классы из разбиения <tex>P</tex> уже были разбиты по <tex>(R, c)</tex>. |
=== Реализация === | === Реализация === | ||
+ | <tex>pushSetsToQueue(S, R_1, R_2, c)</tex> {{---}} функция, которая добавляет одно из <tex>(R_1, c)</tex>, <tex>(R_2, c)</tex> в очередь S. | ||
− | + | <tex>Q</tex> {{---}} множество состояний ДКА. | |
− | + | <tex>F</tex> {{---}} множество терминальных состояний. | |
− | + | <tex>S</tex> {{---}} очередь пар <tex>(C, a)</tex>. | |
− | + | <tex>P</tex> {{---}} разбиение множества состояний ДКА. | |
− | + | <tex>R</tex> {{---}} класс состояний ДКА. | |
− | |||
− | + | <tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex> | |
− | + | <tex>\mathtt{S} \leftarrow \varnothing </tex> | |
− | + | '''for''' <tex>c \in \Sigma</tex> | |
− | + | <tex>pushSetsToQueue(S, F, Q \setminus F, c)</tex> | |
− | + | '''while''' <tex>\mathtt{S} \ne \varnothing</tex> | |
− | + | <tex>(C, a) \leftarrow</tex> '''pop''' '''from''' <tex>\mathtt{S}</tex> | |
− | + | '''for''' <tex>R</tex> '''in''' <tex>\mathtt{P}</tex> | |
− | + | <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>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> | |
− | + | '''for''' <tex>c \in \Sigma</tex> | |
− | + | <tex>pushSetsToQueue(S, R_1, R_2, c)</tex> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Понятно, что нам нет никакой необходимости просматривать все классы в разбиении. Вполне достаточно рассмотреть лишь те классы, из состояний которых есть хотя бы одно ребро в состояния сплиттера. Обозначим множество таких классов за T' (его нужно будет эффективно находить для каждой пары <tex>(C, a)</tex>). | |
− | |||
− | + | <tex>\mathtt{P} \leftarrow \{ \mathtt{F, Q} \setminus \mathtt{F} \}</tex> | |
+ | <tex>\mathtt{S} \leftarrow \varnothing </tex> | ||
+ | '''for''' <tex>c \in \Sigma</tex> | ||
+ | <tex>pushSetsToQueue(S, F, Q \setminus F, c)</tex> | ||
+ | '''while''' <tex>\mathtt{S} \ne \varnothing</tex> | ||
+ | <tex>(C, a) \leftarrow</tex> '''pop''' '''from''' <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''' <tex>R</tex> '''in''' <tex>T'</tex> | ||
+ | <tex> R_1, R_2 \leftarrow </tex> '''split'''(<tex>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> | ||
+ | '''for''' <tex>c \in \Sigma</tex> | ||
+ | <tex>pushSetsToQueue(S, R_1, R_2, c)</tex> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | Каждая итерация цикла <tex> \mathrm{while} </tex> может быть выполнена за <tex> O(|Q| + |\mathtt{Inverse}|) </tex> для текущей пары <tex> (C,a)</tex>. Покажем, как можно достичь этой оценки. | ||
− | + | Классы разбиения <tex>P</tex> будем поддерживать с помощью множеств на хэш-таблицах (само же разбиение - обычный вектор, индекс - номер класса). Это позволит нам эффективно переносить состояния из одного класса в другой (за O(1)). | |
− | + | *<tex>\mathtt{Class}[r]</tex> {{---}} номер класса, которому принадлежит состояние <tex>r</tex> | |
+ | *<tex>\mathtt{Card}[i]</tex> {{---}} размер класса <tex>i</tex> | ||
+ | *<tex>\mathtt{Queue}</tex> {{---}} очередь пар <tex>(C, a)</tex>, где <tex>C</tex> - номер класса (сплиттера) | ||
+ | *<tex>\mathtt{InQueue}[C]</tex> {{---}} множество на хэш-таблице, содержащее символ <tex>a</tex>, если в очереди содержится пара <tex>(C, a)</tex> | ||
+ | *<tex>\mathtt{Inv}[r][a]</tex> {{---}} массив состояний, из которых есть ребра по символу <tex>a</tex> в состояние <tex>r</tex> (мы не меняем исходный автомат, потому может быть построен раз перед началом работы алгоритма) | ||
− | *<tex>\mathtt{ | + | Для обработки <tex>T'</tex> за <tex>O(|Q| + |\mathtt{Inverse}|)</tex> нам понадобится следующая структура: |
− | *<tex>\mathtt{ | + | *<tex>\mathtt{Counter}</tex> {{---}} количество классов; |
− | *<tex>\mathtt{ | + | *<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> F, Q \setminus F </tex> в <tex> \mathtt{Queue} </tex>, <tex>\mathtt{InQueue}</tex> | |
− | + | '''while''' <tex>\mathtt{Queue} \ne \varnothing</tex> | |
− | + | <tex>(C, a) \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{Size}[i] == 0</tex> | |
− | + | '''insert''' <tex>i</tex> '''in''' <tex>\mathtt{Involved}</tex> | |
− | + | <tex>\mathtt{Size}[i]++</tex> | |
− | + | '''for''' <tex> i \in \mathtt{Involved}</tex> | |
− | + | '''if''' <tex>\mathtt{Size}[i] < \mathtt{Card}[i]</tex> | |
− | + | <tex>\mathtt{Size}[i] = -1</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] == -1</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> | |
− | + | '''if''' <tex> \mathtt{Twin}[j] \neq 0 </tex> | |
− | + | '''for''' <tex>c \in \Sigma</tex> | |
− | + | <tex>pushSetsToQueue(\mathtt{Queue}, j, \mathtt{Twin}[j], c)</tex> | |
− | + | <tex>\mathtt{Size}[j] = 0</tex> | |
− | + | <tex>\mathtt{Twin}[j] = 0</tex> | |
− | + | '''remove''' <tex>a</tex> '''from''' <tex>\mathtt{InQueue}[C]</tex> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | Стоит отметить, что массивы <tex>\mathtt{Size}, \mathtt{Twin}</tex> аллоцируются ровно один раз при инициализации алгоритма. | ||
− | + | Осталось только реализовать <tex>pushSetsToQueue</tex>. | |
− | + | <tex>pushSetsToQueue(\mathtt{Queue}, R_1, R_2, c)</tex>: | |
− | + | <tex>cnt1 \leftarrow \mathtt{Card}[R_1]</tex> | |
− | + | <tex>cnt2 \leftarrow \mathtt{Card}[R_2]</tex> | |
+ | '''if''' <tex> \lnot ( \mathtt{InQueue}[R_1] </tex> '''contains''' <tex> c) </tex> '''and''' <tex> cnt1 <= cnt2 </tex> | ||
+ | '''push''' <tex>(R_1, c)</tex> '''to''' <tex>\mathtt{Queue}</tex> | ||
+ | '''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_1]</tex> | ||
+ | '''else''' | ||
+ | '''push''' <tex>(R_2, c)</tex> '''to''' <tex>\mathtt{Queue}</tex> | ||
+ | '''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_2]</tex> | ||
===Время работы=== | ===Время работы=== | ||
Строка 217: | Строка 195: | ||
Количество итераций цикла <tex>\mathrm{while}</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>. | Количество итераций цикла <tex>\mathrm{while}</tex> не превышает <tex> 2 |\Sigma| |Q| </tex>. | ||
|proof = | |proof = | ||
− | Для доказательства этого утверждения достаточно показать, что количество пар <tex> | + | Для доказательства этого утверждения достаточно показать, что количество пар <tex>(C,a)</tex> добавленных в очередь <tex>S</tex> не превосходит <tex> 2 |\Sigma| |Q| </tex>, так как на каждой итерации мы извлекаем одну пару из очереди. |
− | По [[#Лемма1 | лемме(1)]] количество классов не превосходит <tex>2 |Q| - 1</tex>. Пусть <tex>C</tex> элемент текущего разбиения. Тогда количество пар <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: | Строка 204: | ||
|id = Лемма3 | |id = Лемма3 | ||
|statement = | |statement = | ||
− | Пусть <tex>a \in \Sigma</tex> и <tex>p \in Q</tex>. Тогда количество пар <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> | + | Рассмотрим пару <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>. |
}} | }} | ||
Строка 237: | Строка 215: | ||
<tex>\sum |\mathtt{Inverse}|</tex> по всем итерациям цикла <tex>\mathrm{while}</tex> не превосходит <tex>|\Sigma| |Q| \log_2(|Q|)</tex>. | <tex>\sum |\mathtt{Inverse}|</tex> по всем итерациям цикла <tex>\mathrm{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>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> мы получим утверждение леммы. |
}} | }} | ||
Строка 250: | Строка 228: | ||
*По [[#Лемма2 | второй лемме]] количество итераций цикла <tex>\mathrm{while}</tex> не превосходит <tex>O(|\Sigma| |Q|)</tex>. | *По [[#Лемма2 | второй лемме]] количество итераций цикла <tex>\mathrm{while}</tex> не превосходит <tex>O(|\Sigma| |Q|)</tex>. | ||
− | *Операции с множеством <tex>T'</tex> и разбиение классов на подклассы требуют <tex>O(\sum(|\mathtt{Inverse}|)) | + | *Операции с множеством <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: | Строка 235: | ||
}} | }} | ||
− | == | + | == Сравнение с алгоритмом из оригинальной статьи Хопкрафта == |
− | |||
− | + | В [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf оригинальной статье] использовалась дополнительная структура, которую мы обозначим, как <tex>\mathtt{ClassInv}</tex>, в <tex>\mathtt{ClassInv}[C][a]</tex> будем хранить множество состояний, из которых есть ребро по символу <tex>a</tex> в состояние <tex>C</tex> (аналогично <tex>Inv</tex>, только для классов). | |
− | + | <tex>\mathtt{ClassInv}[C][a] = \{ s | \mathtt{Class}[s] == C </tex> and <tex> \delta^{-1} (s, a) \neq \emptyset \}</tex> | |
− | |||
− | |||
− | |||
− | + | <tex>pushSetsToQueue</tex> реализуем так: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | = | + | <tex>pushSetsToQueue(\mathtt{Queue}, R_1, R_2, c)</tex>: |
+ | <tex>cnt1 \leftarrow \mathtt{ClassInv}[R_1][c]</tex> | ||
+ | <tex>cnt2 \leftarrow \mathtt{ClassInv}[R_2][c]</tex> | ||
+ | '''if''' <tex> \lnot ( \mathtt{InQueue}[R_1] </tex> '''contains''' <tex> c) </tex> '''and''' <tex> cnt1 <= cnt2 </tex> | ||
+ | '''push''' <tex>(R_1, c)</tex> '''to''' <tex>\mathtt{Queue}</tex> | ||
+ | '''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_1]</tex> | ||
+ | '''else''' | ||
+ | '''push''' <tex>(R_2, c)</tex> '''to''' <tex>\mathtt{Queue}</tex> | ||
+ | '''insert''' <tex> c </tex> '''into''' <tex>\mathtt{InQueue}[R_2]</tex> | ||
− | + | Циклы | |
− | == | + | '''for''' <tex>q \in C</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex> |
+ | <tex>(...)</tex> | ||
+ | |||
+ | реализуются | ||
+ | |||
+ | '''for''' <tex>q \in \mathtt{ClassInv}[C][a]</tex> '''and''' <tex>r \in \mathtt{Inv}[q][a]</tex> | ||
+ | <tex>(...)</tex>' | ||
+ | |||
+ | Тогда время работы внутреннего цикла можно будет оценить как <tex>O(|\mathtt{ClassInv}[C][a]| + |\mathtt{Inverse}|)</tex>. А реализация <tex>pushSetsToQueue</tex> выбирает множество, на котором <tex>O(|\mathtt{ClassInv}[C][a]|)</tex> будет меньшим. | ||
+ | |||
+ | Кроме того, вместо хэш-таблиц для хранения множеств (<tex>\mathtt{ClassInv}</tex>, разбиение <tex>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 | + | * [http://i.stanford.edu/pub/cstr/reports/cs/tr/71/190/CS-TR-71-190.pdf Оригинальная статья Хопкрофта] |
− | |||
[[Категория: Теория формальных языков]] | [[Категория: Теория формальных языков]] | ||
[[Категория: Автоматы и регулярные языки]] | [[Категория: Автоматы и регулярные языки]] |