Анализ свойств регулярных языков (пустота, совпадение, включение, конечность, подсчёт числа слов) — различия между версиями
Shevchen (обсуждение | вклад) м |
Shevchen (обсуждение | вклад) м |
||
| Строка 16: | Строка 16: | ||
|proof= | |proof= | ||
| − | Пусть язык содержит слово <tex>w</tex>. Любой автомат <tex>A</tex>, задающий этот язык, должен допускать <tex>w</tex>. Тогда при переходе из стартового состояния <tex>A</tex> по символам <tex>w</tex> получится путь, оканчивающийся в | + | Пусть язык содержит слово <tex>w</tex>. Любой автомат <tex>A</tex>, задающий этот язык, должен допускать <tex>w</tex>. Тогда при переходе из стартового состояния <tex>A</tex> по символам <tex>w</tex> получится путь, оканчивающийся в одном из терминальных состояний. |
| − | Пусть в автомате существует путь из стартового состояния в одно из допускающих. Рассмотрим последовательность символов на | + | Пусть в автомате существует путь из стартового состояния в одно из допускающих. Рассмотрим последовательность символов на переходах, образующих этот путь. Строка из этой последовательности допускается автоматом, а значит, принадлежит языку. |
}} | }} | ||
| Строка 51: | Строка 51: | ||
}} | }} | ||
| − | Пусть <tex>A_{1}</tex> и <tex>A_{2}</tex> | + | Пусть <tex>A_{1}</tex> и <tex>A_{2}</tex> — детерминированные конечные автоматы, соответствующие языкам <tex>L_{1}</tex> и <tex>L_{2}</tex> над одним алфавитом <tex>\Sigma</tex>, соответственно. Совпадение языков на языке конечных автоматов (''эквивалентность'') означает, что любое слово, допустимое одним автоматом, допускается и другим. Назовём состояния <tex>p_{1} \in A_{1}</tex> и <tex>p_{2} \in A_{2}</tex> '''различимыми''', если существует строка <tex>w</tex> из символов <tex>\Sigma</tex>, для которой выполняется |
<tex>\langle p_{1}, w \rangle \rightarrow \langle t_{1}, \epsilon \rangle</tex>, <tex>\langle p_{2}, w \rangle \rightarrow \langle u_{2}, \epsilon \rangle</tex> | <tex>\langle p_{1}, w \rangle \rightarrow \langle t_{1}, \epsilon \rangle</tex>, <tex>\langle p_{2}, w \rangle \rightarrow \langle u_{2}, \epsilon \rangle</tex> | ||
| Строка 59: | Строка 59: | ||
<tex>\langle p_{1}, w \rangle \rightarrow \langle u_{1}, \epsilon \rangle</tex>, <tex>\langle p_{2}, w \rangle \rightarrow \langle t_{2}, \epsilon \rangle</tex>, | <tex>\langle p_{1}, w \rangle \rightarrow \langle u_{1}, \epsilon \rangle</tex>, <tex>\langle p_{2}, w \rangle \rightarrow \langle t_{2}, \epsilon \rangle</tex>, | ||
| − | где <tex>s_{1}</tex>, <tex>s_{2}</tex> | + | где <tex>s_{1}</tex>, <tex>s_{2}</tex> — стартовые состояния, <tex>t_{1}</tex>, <tex>t_{2}</tex> — допускающие состояния, <tex>u_{1}</tex>, <tex>u_{2}</tex> — недопускающие. |
| − | Все | + | Все состояния, из которых не достигаются допускающие, не влияют на множество слов, допускаемых автоматами; назовём их '''бесполезными'''. Введём '''сток''' — специальное недопускающее состояние, переходы по всем символам из которого ведут в него самого. Все переходы исходного автомата, которые отсутствовали или вели в бесполезные состояния, направим в сток. |
| Строка 69: | Строка 69: | ||
| − | Пусть <tex>eq(u, v)</tex> | + | Пусть <tex>eq(u, v)</tex> — функция, принимающая пару состояний из первого и второго автоматов и возвращающая некоторое значение булевского типа. Второй шаг алгоритма — установка <tex>eq(u, v)</tex> в <tex>false</tex> для всех пар <tex>\langle u, v \rangle</tex>, кроме <tex>\langle s_{1}, s_{2} \rangle</tex>. Также создаётся очередь, в которую помещается пара <tex>\langle s_{1}, s_{2} \rangle</tex>. |
| − | Третий шаг алгоритма | + | Третий шаг алгоритма — [[Обход в ширину|обход в ширину]]. Пусть на текущем шаге из очереди получена пара <tex>\langle u \in A_{1}, v \in A_{2} \rangle</tex>. Тогда для всех символов <tex>c \in \Sigma</tex> рассматриваются пары <tex>\langle u', v' \rangle : \delta_{1} (u, c) = u', \delta_{2} (v, c) = v'</tex>. Если <tex>eq(u', v')</tex> возвращает <tex>false</tex>, данное значение устанавливается в <tex>true</tex>, а в очередь добавляется пара <tex>\langle u', v' \rangle</tex>. |
| Строка 87: | Строка 87: | ||
| − | Пусть такая пара <tex>\langle u, v \rangle</tex> существует. Для определённости скажем, что <tex>u \in A_{1}</tex> | + | Пусть такая пара <tex>\langle u, v \rangle</tex> существует. Для определённости скажем, что <tex>u \in A_{1}</tex> — допускающее. Рассмотрим строку <tex>w</tex>, состоящую из символов, в результате переходов по которым из <tex>\langle s_{1}, s_{2} \rangle</tex> в процессе обхода в ширину <tex>eq(u, v)</tex> было установлено в <tex>true</tex>. Строка <tex>w</tex> допускается первым автоматом, но не допускается вторым, значит, автоматы не эквивалентны. |
}} | }} | ||
| Строка 152: | Строка 152: | ||
=== Алгоритм проверки на включение === | === Алгоритм проверки на включение === | ||
| − | Алгоритм проверки <tex>L_{1}</tex> на включение в <tex>L_{2}</tex> идентичен алгоритму проверки их совпадения, кроме одной особенности. Могут существовать слова из <tex>L_{2}</tex>, не входящие в <tex>L_{1}</tex>, поэтому существование пар <tex>\langle v \in L_{1}, u \in L_{2} \rangle : eq(v, u) == true, v \notin T_{1}, u \in T_{2}</tex>, где <tex>T_{i}</tex> | + | Алгоритм проверки <tex>L_{1}</tex> на включение в <tex>L_{2}</tex> идентичен алгоритму проверки их совпадения, кроме одной особенности. Могут существовать слова из <tex>L_{2}</tex>, не входящие в <tex>L_{1}</tex>, поэтому существование пар <tex>\langle v \in L_{1}, u \in L_{2} \rangle : eq(v, u) == true, v \notin T_{1}, u \in T_{2}</tex>, где <tex>T_{i}</tex> — множества допускающих состояний, не нарушает факт вхождения <tex>L_{1}</tex> в <tex>L_{2}</tex>. Таким образом, <tex>L_{1}</tex> не входит в <tex>L_{2}</tex> тогда и только тогда, когда после окончания работы алгоритма, идентичного алгоритму проверки на совпадение, не существует такой пары <tex>\langle v, u \rangle</tex>, что <tex>eq(v, u)</tex> возвращает <tex>true</tex>, <tex>v \in T_{1}, u \notin T_{2}</tex>. |
==== Псевдокод ==== | ==== Псевдокод ==== | ||
| Строка 219: | Строка 219: | ||
Автомат <tex>A_{1}</tex> задаёт конечный язык тогда и только тогда, когда в <tex>A_{1}</tex> не существует состояния <tex>v</tex>, для которого выполняются три условия: | Автомат <tex>A_{1}</tex> задаёт конечный язык тогда и только тогда, когда в <tex>A_{1}</tex> не существует состояния <tex>v</tex>, для которого выполняются три условия: | ||
1) <tex>v</tex> достижимо из стартового состояния <tex>s</tex>; | 1) <tex>v</tex> достижимо из стартового состояния <tex>s</tex>; | ||
| + | |||
2) из <tex>v</tex> достижимо какое-либо из допускающих состояний; | 2) из <tex>v</tex> достижимо какое-либо из допускающих состояний; | ||
| + | |||
3) из <tex>v</tex> по одному или более переходам достижимо <tex>v</tex>. | 3) из <tex>v</tex> по одному или более переходам достижимо <tex>v</tex>. | ||
|proof= | |proof= | ||
| − | Пусть такое состояние <tex>v</tex> существует, а строки <tex>x, y, z</tex> таковы, что <tex>\langle s, xyz \rangle \vdash ^{*} \langle v, yz \rangle \vdash ^{*} \langle v, z \rangle \vdash ^{*} \langle t, \epsilon \rangle</tex>, <tex>t</tex> | + | Пусть такое состояние <tex>v</tex> существует, а строки <tex>x, y, z</tex> таковы, что <tex>\langle s, xyz \rangle \vdash ^{*} \langle v, yz \rangle \vdash ^{*} \langle v, z \rangle \vdash ^{*} \langle t, \epsilon \rangle</tex>, <tex>t</tex> — допускающее, <tex>y</tex> — непустая. Рассмотрим строки вида <tex>xy^{k}z, k \in \mathbb{N}</tex>. Их бесконечное количество, и все они, как легко увидеть, допускаются автоматом. Значит, язык бесконечен. |
| − | Пусть такого состояния не существует. Тогда любой путь из стартового состояния в какое-либо из допускающих является простым. Количество слов в языке равно количеству таких путей; количество путей, в свою очередь, ограничено <tex>n! | + | Пусть такого состояния не существует. Тогда любой путь из стартового состояния в какое-либо из допускающих является простым. Количество слов в языке равно количеству таких путей; количество путей, в свою очередь, ограничено <tex>n! | \Sigma |^{n-1}</tex>, где <tex>n</tex> — количество состояний автомата: <tex>n!</tex> — количество перестановок состояний, <tex>| \Sigma |^{n-1}</tex> — количество совокупностей переходов по символам между ними. Таким образом, язык конечен. |
}} | }} | ||
=== Алгоритм нахождения числа слов в языке === | === Алгоритм нахождения числа слов в языке === | ||
| − | Доказанное утверждение позволяет свести задачу поиска числа слов в языке к поиску количества различных путей в ациклическом графе. Сначала с помощью [[Обход в глубину, цвета вершин|обхода в глубину]] по обратным рёбрам определим ''полезные'' состояния, из которых достижимо хотя бы одно допускающее. Затем найдём любой цикл, состояния которого полезны, достижимый из старта; при нахождении констатируем бесконечность языка. Пусть язык конечен; тогда отсортируем автомат [[Использование обхода в глубину для топологической сортировки|топологически]]. Введём функцию <tex>paths(v)</tex>, задающую число различных путей из <tex>s</tex> в <tex>v</tex>; <tex>paths(s) = 1</tex>. Заметим, что если известны значения <tex>paths(u)</tex> для всех <tex>u</tex>, из которых существует переход в <tex>v</tex>, то <tex>paths(v) = \sum\limits_{u}paths(u)</tex>. Количеством слов в языке будет сумма <tex>paths(t)</tex> для всех допускающих <tex>t</tex>. | + | Доказанное утверждение позволяет свести задачу поиска числа слов в языке к поиску количества различных путей в ациклическом графе. Сначала с помощью [[Обход в глубину, цвета вершин|обхода в глубину]] по обратным рёбрам определим '''полезные''' состояния, из которых достижимо хотя бы одно допускающее. Затем найдём любой цикл, состояния которого полезны, достижимый из старта; при нахождении констатируем бесконечность языка. Пусть язык конечен; тогда отсортируем автомат [[Использование обхода в глубину для топологической сортировки|топологически]]. Введём функцию <tex>paths(v)</tex>, задающую число различных путей из <tex>s</tex> в <tex>v</tex>; <tex>paths(s) = 1</tex>. Заметим, что если известны значения <tex>paths(u)</tex> для всех <tex>u</tex>, из которых существует переход в <tex>v</tex>, то <tex>paths(v) = \sum\limits_{u}paths(u)</tex>. Количеством слов в языке будет сумма <tex>paths(t)</tex> для всех допускающих <tex>t</tex>. |
Топологическую сортировку и поиск цикла можно объединить в один обход, но для наглядности они были разделены. | Топологическую сортировку и поиск цикла можно объединить в один обход, но для наглядности они были разделены. | ||
Версия 08:17, 28 октября 2011
Содержание
Пустота
| Определение: |
| Регулярный язык называется пустым, если он не содержит ни одного слова. |
Язык, содержащий хотя бы одно слово, назовём непустым.
| Утверждение: |
Регулярный язык является непустым тогда и только тогда, когда в любом задающем его автомате существует путь из стартового состояния в какое-либо из терминальных. |
|
Пусть язык содержит слово . Любой автомат , задающий этот язык, должен допускать . Тогда при переходе из стартового состояния по символам получится путь, оканчивающийся в одном из терминальных состояний.
|
Алгоритм проверки языка на пустоту
Для определения пустоты языка по соответствующему ему автомату проще всего использовать алгоритм обхода в глубину. Язык не является пустым тогда и только тогда, когда при поиске из стартового состояния автомата окажется достижимой хотя бы одна терминальная вершина.
Псевдокод
boolean dfs(State v):
v.seen = true
if v.isFinal:
return false
for each State u in v.next:
if !u.seen && !dfs(u):
return false
return true
boolean isEmpty(Automaton a):
for each State v in a:
v.seen = false
return dfs(a.start)
Совпадение
| Определение: |
| Два регулярных языка совпадают, если любое слово или содержится в обоих языках, или не содержится ни в одном из них. |
Пусть и — детерминированные конечные автоматы, соответствующие языкам и над одним алфавитом , соответственно. Совпадение языков на языке конечных автоматов (эквивалентность) означает, что любое слово, допустимое одним автоматом, допускается и другим. Назовём состояния и различимыми, если существует строка из символов , для которой выполняется
,
или
, ,
где , — стартовые состояния, , — допускающие состояния, , — недопускающие.
Все состояния, из которых не достигаются допускающие, не влияют на множество слов, допускаемых автоматами; назовём их бесполезными. Введём сток — специальное недопускающее состояние, переходы по всем символам из которого ведут в него самого. Все переходы исходного автомата, которые отсутствовали или вели в бесполезные состояния, направим в сток.
Алгоритм проверки языков на совпадение
Первым шагом алгоритма является избавление автоматов от состояний, из которых недостижимы допускающие. Проще всего это реализовать обходом в глубину или в ширину из допускающих состояний по обратным рёбрам. Все непосещённые состояния затем удаляются из автоматов, вместо них вводится описанный выше сток.
Пусть — функция, принимающая пару состояний из первого и второго автоматов и возвращающая некоторое значение булевского типа. Второй шаг алгоритма — установка в для всех пар , кроме . Также создаётся очередь, в которую помещается пара .
Третий шаг алгоритма — обход в ширину. Пусть на текущем шаге из очереди получена пара . Тогда для всех символов рассматриваются пары . Если возвращает , данное значение устанавливается в , а в очередь добавляется пара .
| Утверждение: |
Автоматы и эквивалентны тогда и только тогда, когда после окончания работы алгоритма не существует такой пары , что возвращает и ровно одно из допускающее. |
|
Пусть такой пары не существует. Возьмём произвольное слово длины и выпишем последовательность пар состояний : и справедливо . Так как пара была в очереди, каждая из последующих пар в процессе алгоритма также побывала в очереди, значит, для них возвращает . По предположению, или оба состояния допускающие в своих автоматах, или оба недопускающие. Таким образом, строка или входит в оба языка, или не входит ни в один.
|
Псевдокод
void reverseDfs(State v):
v.canReach = true
for each State u in v.prev:
if !u.canReach:
reverseDfs(u)
void setSink(Automaton a):
State sink = new State
for each symbol c in a.alphabet:
sink.next(c) = sink
for each State v in a:
if !v.canReach:
v = sink
void bfs(Automaton a, Automaton b, boolean[][] eq)
fill(eq, false)
eq[a.start][b.start] = true
Queue q = new Queue
q.add((a.start, b.start))
while !q.isEmpty:
(v, u) = q.remove()
for each symbol c in a.alphabet: // a.alphabet == b.alphabet
v' = v.next(c)
u' = u.next(c)
if !eq[v'][u']:
eq[v'][u'] = true
q.add((v', u'))
boolean areEqual(Automaton a, Automaton b)
for each State v in a:
v.canReach = false
for each State v in a:
if v.isFinal:
reverseDfs(v)
setSink(a)
for each State v in b:
v.canReach = false
for each State v in b:
if v.isFinal:
reverseDfs(v)
setSink(b)
eq = new boolean[a.statesNumber][b.statesNumber]
bfs(a, b, eq)
for each State v in a:
for each State u in b:
if eq[v][u] && v.isFinal != u.isFinal:
return false
return true
Включение
| Определение: |
| Регулярный язык входит (включается) в регулярный язык , если любое слово, принадлежащее , принадлежит . |
Алгоритм проверки на включение
Алгоритм проверки на включение в идентичен алгоритму проверки их совпадения, кроме одной особенности. Могут существовать слова из , не входящие в , поэтому существование пар , где — множества допускающих состояний, не нарушает факт вхождения в . Таким образом, не входит в тогда и только тогда, когда после окончания работы алгоритма, идентичного алгоритму проверки на совпадение, не существует такой пары , что возвращает , .
Псевдокод
void reverseDfs(State v):
v.canReach = true
for each State u in v.prev:
if !u.canReach:
reverseDfs(u)
void setSink(Automaton a):
State sink = new State
for each symbol c in a.alphabet:
sink.next(c) = sink
for each State v in a:
if !v.canReach:
v = sink
void bfs(Automaton a, Automaton b, boolean[][] eq)
fill(eq, false)
eq[a.start][b.start] = true
Queue q = new Queue
q.add((a.start, b.start))
while !q.isEmpty:
(v, u) = q.remove()
for each symbol c in a.alphabet: // a.alphabet == b.alphabet
v' = v.next(c)
u' = u.next(c)
if !eq[v'][u']:
eq[v'][u'] = true
q.add((v', u'))
boolean belongs(Automaton a, Automaton b)
for each State v in a:
v.canReach = false
for each State v in a:
if v.isFinal:
reverseDfs(v)
setSink(a)
for each State v in b:
v.canReach = false
for each State v in b:
if v.isFinal:
reverseDfs(v)
setSink(b)
eq = new boolean[a.statesNumber][b.statesNumber]
bfs(a, b, eq)
for each State v in a:
for each State u in b:
if eq[v][u] && v.isFinal && !u.isFinal:
return false
return true
Конечность языка, подсчёт числа слов
| Определение: |
| Регулярный язык называется конечным, если принадлежащее ему множество слов конечно. |
| Утверждение: |
Автомат задаёт конечный язык тогда и только тогда, когда в не существует состояния , для которого выполняются три условия:
1) достижимо из стартового состояния ; 2) из достижимо какое-либо из допускающих состояний; 3) из по одному или более переходам достижимо . |
|
Пусть такое состояние существует, а строки таковы, что , — допускающее, — непустая. Рассмотрим строки вида . Их бесконечное количество, и все они, как легко увидеть, допускаются автоматом. Значит, язык бесконечен.
|
Алгоритм нахождения числа слов в языке
Доказанное утверждение позволяет свести задачу поиска числа слов в языке к поиску количества различных путей в ациклическом графе. Сначала с помощью обхода в глубину по обратным рёбрам определим полезные состояния, из которых достижимо хотя бы одно допускающее. Затем найдём любой цикл, состояния которого полезны, достижимый из старта; при нахождении констатируем бесконечность языка. Пусть язык конечен; тогда отсортируем автомат топологически. Введём функцию , задающую число различных путей из в ; . Заметим, что если известны значения для всех , из которых существует переход в , то . Количеством слов в языке будет сумма для всех допускающих .
Топологическую сортировку и поиск цикла можно объединить в один обход, но для наглядности они были разделены.
Псевдокод
Stack topSort(Automaton a):
for each State v in a:
v.seen = false
Stack sorted = new Stack
dfsSort(a.start, sorted)
return sorted
void dfsSort(State v, Stack sorted):
v.seen = true
for each State u in v.next:
if !u.seen:
dfsSort(u, sorted)
sorted.push(v)
void reverseDfs(State v):
v.canReach = true
for each State u in v.prev:
if !u.canReach:
reverseDfs(u)
boolean dfs(State v): // returns true iff there is a cycle
v.color = GREY
for each State u in v.next:
if u.color == GREY:
return true
if u.canReach && u.color == WHITE && dfs(u):
return true
v.color = BLACK
return false
int words(Automaton a):
for each State v in a:
v.canReach = false
for each State v in a:
if v.isFinal:
reverseDfs(v)
for each State v in a:
v.color = WHITE
if dfs(a.start):
return infinity
Stack sorted = topSort(a)
paths = new int[a.statesNumber]
fill(paths, 0)
paths[0] = 1
while !sorted.isEmpty:
State v = sorted.pop()
for each State u in v.next:
paths[u] += paths[v]
int result = 0
for each State v in a:
if v.isFinal:
result += paths[v]
return result