Суффиксный автомат — различия между версиями
м (rollbackEdits.php mass rollback) |
|||
(не показано 30 промежуточных версий 9 участников) | |||
Строка 6: | Строка 6: | ||
__TOC__ | __TOC__ | ||
==Описание== | ==Описание== | ||
− | Суффиксный автомат <tex>A</tex> | + | Рассмотрим конечный алфавит <tex>A</tex>. Пусть <tex>A^*</tex> {{---}} набор слов в алфавите <tex>A</tex>. Суффиксный автомат <tex>\mathcal{A}</tex> {{---}} это набор <tex>\langle Q, A, i, T, \delta \rangle</tex>, где |
+ | * <tex>Q</tex> {{---}} конечный набор состояний, | ||
+ | * <tex>i</tex> {{---}} начальное состояние, | ||
+ | * <tex>T</tex> {{---}} набор терминальных состояний, | ||
+ | * <tex>\delta</tex> {{---}} функция перехода. | ||
+ | Для <tex>q \in Q</tex> и <tex>a \in A</tex>, <tex>\delta(q, a)</tex> определена, если состояние достижимо из <tex>q</tex> переходом по символу <tex>a</tex>. Функция перехода распространяется на слова и <tex>\delta(q, x)</tex> обозначает, что если она существует, то состояние достигнуто после чтения слова <tex>x</tex> из состояния <tex>q</tex>. | ||
+ | Автомат <tex>\mathcal{A}</tex> распознает язык <tex>\{x \in A^* : \delta(i, x) \in T \}</tex>. | ||
+ | Суффиксный автомат <tex>\mathcal{A}</tex> для строки <tex>s</tex> представляет собой [[Основные_определения_теории_графов|ациклический ориентированный граф]], с начальной вершиной и множеством терминальных вершин, рёбра которого помечены символами <tex>s</tex>: | ||
+ | * вершины этого графа {{---}} состояния автомата, а рёбра {{---}} переходы, | ||
+ | * каждый переход в автомате {{---}} ребро в графе, помеченное некоторым символом и все рёбра, исходящие из одной вершины имеют разные метки, | ||
+ | * одно из состояний называется начальным, из него достижимы все остальные состояния, | ||
+ | * одно или несколько состояний помечены как терминальные {{---}} если пройти от начального состояния до терминального по какому-либо пути и выписывать при этом символы на переходах, то получим один из суффиксов строки <tex>s</tex>. | ||
+ | <br> | ||
[[Файл:Suffix_automaton_ex.png|540px|frame|center|Пример суффиксного автомата для строки <tex>abbab</tex>.]] | [[Файл:Suffix_automaton_ex.png|540px|frame|center|Пример суффиксного автомата для строки <tex>abbab</tex>.]] | ||
Строка 35: | Строка 47: | ||
}} | }} | ||
Таким образом, ДКА является минимальным тогда и только тогда, когда правые контексты всех его состояний попарно различны. | Таким образом, ДКА является минимальным тогда и только тогда, когда правые контексты всех его состояний попарно различны. | ||
− | В случае суффиксного автомата правый контекст <tex>X_a</tex> строки <tex>a</tex> взаимнооднозначно соответствует множеству правых позиций вхождений строки <tex>a</tex> в строку <tex>s</tex>. Таким образом, каждое состояние автомата принимает строки с одинаковым множеством правых позиций их вхождений и обратно, все строки с таким множеством позиций принимается этим состоянием. | + | В случае суффиксного автомата правый контекст <tex>X_a</tex> строки <tex>a</tex> взаимнооднозначно соответствует множеству правых позиций вхождений строки <tex>a</tex> в строку <tex>s</tex>. Таким образом, каждое состояние автомата принимает строки с одинаковым множеством правых позиций их вхождений и обратно, все строки с таким множеством позиций принимается этим состоянием. |
==Построение== | ==Построение== | ||
Строка 41: | Строка 53: | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | Пусть длина самой короткой строки, которая принимается состоянием <tex>q</tex> равно <tex>k</tex>, тогда '''суффиксная ссылка''' <tex>link_q</tex> будет вести из этого состояния в состояние, которое принимает эту же строку без первого символа. | + | Пусть длина самой короткой строки, которая принимается состоянием <tex>q</tex> равно <tex>k</tex>, тогда '''суффиксная ссылка''' <tex>\mathrm{link_q}</tex> будет вести из этого состояния в состояние, которое принимает эту же строку без первого символа. |
}} | }} | ||
− | Будем обозначать длину самой длинной строки, которая принимается состоянием <tex>q</tex> как <tex>len_q</tex>. Длина самой короткой строки из <tex>q</tex> в таком случае будет равна <tex>len(link_q) + 1</tex>. | + | Будем обозначать длину самой длинной строки, которая принимается состоянием <tex>q</tex> как <tex>\mathrm{len_q}</tex>. Длина самой короткой строки из <tex>q</tex> в таком случае будет равна <tex>\mathrm{len(link_q)} + 1</tex>. |
− | Суффиксный автомат может быть построен за линейное время online-алгоритмом. Будем добавлять символы строки <tex>s</tex> по одному, перестраивая при этом автомат. | + | Суффиксный автомат может быть построен за линейное время (при константном размере алфавита) online-алгоритмом. Будем добавлять символы строки <tex>s</tex> по одному, перестраивая при этом автомат. |
− | Изначально автомат состоит из одного состояния, для которого <tex>len(0) = 0</tex>, а <tex>link_0 = -1</tex>. | + | Изначально автомат состоит из одного состояния, для которого <tex>\mathrm{len(0)} = 0</tex>, а <tex>\mathrm{link_0} = -1</tex>. |
− | <br>Обозначим состояние <tex>last</tex>, соответствующее текущей строке до добавления символа <tex>c</tex> (изначально <tex>last = 0</tex>). <br>Создадим новое состояние <tex>cur</tex>, <tex>len(cur) = len(last) + 1</tex>. <br>Рассмотрим все переходы из <tex>last</tex> по текущему символу <tex>c</tex>. Если перехода нет, то добавляем переход в <tex>cur</tex>, переходим по суффиксной ссылке и повторяем процедуру снова. Если переход существует, то остановимся и обозначим текущее состояние за <tex>p</tex>. Если перехода не нашлось и по суффиксным ссылкам мы дошли до фиктивного состояния (на которое указывает <tex>link_0</tex>), то <tex>link_{cur} = 0</tex>.<br> | + | <br>Обозначим состояние <tex>\mathrm{last}</tex>, соответствующее текущей строке до добавления символа <tex>c</tex> (изначально <tex>\mathrm{last} = 0</tex>). <br>Создадим новое состояние <tex>\mathrm{cur}</tex>, <tex>\mathrm{len(cur)} = \mathrm{len(last)} + 1</tex>. <br>Рассмотрим все переходы из <tex>\mathrm{last}</tex> по текущему символу <tex>c</tex>. Если перехода нет, то добавляем переход в <tex>\mathrm{cur}</tex>, переходим по суффиксной ссылке и повторяем процедуру снова. Если переход существует, то остановимся и обозначим текущее состояние за <tex>p</tex>. Если перехода не нашлось и по суффиксным ссылкам мы дошли до фиктивного состояния (на которое указывает <tex>\mathrm{link_0}</tex>), то <tex>\mathrm{link_{cur}} = 0</tex>.<br> |
Допустим, что мы остановились в состоянии <tex>p</tex>, из которого существует переход с символом <tex>c</tex>. Обозначим состояние, куда ведёт переход, через <tex>q</tex>. Рассмотрим два случая:<br> | Допустим, что мы остановились в состоянии <tex>p</tex>, из которого существует переход с символом <tex>c</tex>. Обозначим состояние, куда ведёт переход, через <tex>q</tex>. Рассмотрим два случая:<br> | ||
− | # Если <tex>len(p) + 1 = len(q)</tex>, то <tex>link( | + | # Если <tex>\mathrm{len(p)} + 1 = \mathrm{len(q)}</tex>, то <tex>\mathrm{link(cur)} = \mathrm{q}</tex>.<br> |
− | # В противном случае, создадим новое состояние <tex>new</tex>, скопируем в него <tex>q</tex> вместе с суффиксными ссылками и переходами. <tex>len(new)</tex> присвоим значение <tex>len(p) + 1</tex>. Перенаправим суффиксную ссылку из <tex>q</tex> в <tex>new</tex> и добавим ссылку из <tex>cur</tex> в <tex>new</tex>. Пройдём по всем суффиксным ссылкам из состояния <tex>p</tex> и все переходы в состояние <tex>q</tex> по символу <tex>c</tex> перенаправим в <tex>new</tex>. | + | # В противном случае, создадим новое состояние <tex>\mathrm{new}</tex>, скопируем в него <tex>q</tex> вместе с суффиксными ссылками и переходами. <tex>\mathrm{len(new)}</tex> присвоим значение <tex>\mathrm{len(p)} + 1</tex>. Перенаправим суффиксную ссылку из <tex>q</tex> в <tex>\mathrm{new}</tex> и добавим ссылку из <tex>\mathrm{cur}</tex> в <tex>\mathrm{new}</tex>. Пройдём по всем суффиксным ссылкам из состояния <tex>p</tex> и все переходы в состояние <tex>q</tex> по символу <tex>c</tex> перенаправим в <tex>\mathrm{new}</tex>. |
− | Обновим значение <tex>last = cur</tex>. | + | Обновим значение <tex>\mathrm{last} = \mathrm{cur}</tex>. |
+ | |||
===Пример построения=== | ===Пример построения=== | ||
− | + | {| class = "wikitable" | |
− | + | ! Изображение !! Описание | |
− | + | |- | |
− | + | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s1.png|x180px|center|Шаг 1.]] | |
− | + | |Изначально автомат состоит из одного начального состояния. <tex>\mathrm{last} = 0, \mathrm{len(0)} = 0</tex> | |
− | + | |- | |
− | + | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s2.png|x180px|center|Шаг 2.]] | |
− | + | |Добавляем символ <tex>a</tex>. Создаем состояние <tex>1</tex>. Переходов из начального состояния по символу <tex>a</tex> нет, перейти по суффиксным ссылкам некуда, значит добавим суффиксную ссылку <tex>\mathrm{link_{1}} = 0</tex>. <tex>\mathrm{last} = 1, \mathrm{len(1)} = 1</tex> | |
− | + | |- | |
− | + | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s3.png|x180px|center|Шаг 3.]] | |
− | + | |Добавляем символ <tex>b</tex>. Создаем состояние <tex>2</tex>. Добавим переход из <tex>1</tex>, откатимся по суффиксной ссылке и добавим переход из <tex>0</tex>. Добавим суффиксную ссылку <tex>\mathrm{link_{2}} = 0</tex>. <tex>\mathrm{last} = 2, \mathrm{len(2)} = 2</tex> | |
+ | |- | ||
+ | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s4.png|x180px|center|Шаг 4.]] | ||
+ | |Аналогично добавим символ <tex>c</tex> и обновим автомат. <tex>\mathrm{last} = 3, \mathrm{len(3)} = 3</tex> | ||
+ | |- | ||
+ | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s5.png|x180px|center|Шаг 5.]] | ||
+ | |Добавляем символ <tex>b</tex>. Добавим переход из <tex>3</tex> и перейдем по суффиксной ссылке в начальное состояние. Из состояния <tex>0</tex> существует переход по символу <tex>b</tex> | ||
+ | |- | ||
+ | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s6.png|x180px|center|Шаг 6.]] | ||
+ | |Рассмотрим состояние <tex>2</tex>, куда существует переход. Имеем <tex>\mathrm{len(0)} + 1 \neq \mathrm{len(2)}</tex>. | ||
+ | # Создаем новое состояние <tex>5</tex>. | ||
+ | # Копируем в него все суффиксные ссылки и переходы из <tex>2</tex> и присвоим <tex>\mathrm{len(5)} = \mathrm{len(0)} + 1 = 1</tex>. | ||
+ | # Перенаправим суффиксную ссылку из <tex>2</tex> в <tex>5</tex> и добавим ссылку из <tex>4</tex> в <tex>5</tex>. Перенаправим переход <tex>0 \rightarrow 2</tex> в состояние <tex>5</tex>. | ||
+ | |- | ||
+ | |style="background-color:#FFF" |[[Файл:Suffix_automaton_s7.png|x180px|center|Шаг 7.]] | ||
+ | |Построение автомата завершено. Чтобы пометить терминальные вершины, найдём состояние, которое принимает строку <tex>abcb</tex> и пройдём по суффиксным ссылкам, помечая все посещенные состояния терминальными. | ||
+ | |- | ||
+ | |} | ||
==Реализация== | ==Реализация== | ||
− | * | + | В приведённой ниже реализации используются следующие переменные: |
− | * | + | * <tex>\mathrm{edges[]}</tex> {{---}} массив отображений (ключ {{---}} символ, значение {{---}} номер состояния) с переходами, |
− | * | + | * <tex>\mathrm{link[]}</tex> {{---}} массив суффиксных ссылок, |
− | * | + | * <tex>\mathrm{len[]}</tex> {{---}} массив длин строк, |
− | * | + | * <tex>\mathrm{newState()}</tex> {{---}} функция, которая создаёт новое состояние и возвращает его номер, |
+ | * <tex>\mathrm{clone()}</tex> {{---}} функция, которая копирует состояние и возвращает номер нового состояния, | ||
+ | * <tex>\mathrm{last}</tex> {{---}} последнее состояние. | ||
− | '''func''' addChar(c)''':''' | + | '''func''' addChar(c ''': char''')''':''' |
− | cur = newState() <font color="green">// создаём новое состояние и | + | '''int''' cur = newState() <font color="green">// создаём новое состояние и получаем его номер</font> |
− | p = last | + | '''int''' p = last |
− | '''while''' p >= 0 '''and''' edges[p].find(c) == | + | '''while''' p >= 0 '''and''' edges[p].find(c) == ''null'' |
edges[p][c] = cur | edges[p][c] = cur | ||
p = link[p] | p = link[p] | ||
− | '''if''' p != -1''' | + | '''if''' p != -1 |
− | + | '''int''' q = edges[p][c] | |
− | '''if''' len[p] + 1 == len[q] | + | '''if''' len[p] + 1 == len[q] |
link[cur] = q | link[cur] = q | ||
− | '''else | + | '''else''' |
− | new = clone(q) <font color="green">// скопируем состояние <tex>q</tex></font> | + | '''int''' new = clone(q) <font color="green">// скопируем состояние <tex>q</tex> и получим номер нового состояния</font> |
len[new] = len[p] + 1 | len[new] = len[p] + 1 | ||
link[q] = link[cur] = new | link[q] = link[cur] = new | ||
− | '''while''' p >= 0 '''and''' edges[p][c] == q | + | '''while''' p >= 0 '''and''' edges[p][c] == q |
edges[p][c] = new | edges[p][c] = new | ||
p = link[p] | p = link[p] | ||
last = cur | last = cur | ||
+ | |||
+ | ==Применение== | ||
+ | ===Проверка вхождения строки=== | ||
+ | {{Задача | ||
+ | |definition = | ||
+ | Даны строки <tex>s</tex> и <tex>p</tex>. Требуется проверить, является ли строка <tex>p</tex> подстрокой <tex>s</tex>. | ||
+ | }} | ||
+ | Построим суффиксный автомат для строки <tex>s</tex>.<br> | ||
+ | Пусть текущее состояние {{---}} <tex>\mathrm{cur}</tex>, изначально равно <tex>0</tex> (начальному состоянию).<br> | ||
+ | Будем по очереди обрабатывать символы строки <tex>p</tex>. Если из состояния <tex>\mathrm{cur}</tex> есть переход в по текущему символу, то перейдем в новое состояние и повторим процедуру. Если перехода не существует, то <tex>p</tex> не является подстрокой <tex>s</tex>. Если успешно обработали все символы <tex>p</tex>, то она является подстрокой <tex>s</tex>.<br>Асимптотика {{---}} построение суфавтомата за <tex>O(|s|)</tex>, проверка за <tex>O(|p|)</tex>. | ||
+ | |||
+ | ===Позиция первого вхождения строки=== | ||
+ | {{Задача | ||
+ | |definition = | ||
+ | Даны строки <tex>s</tex> и <tex>p</tex>. Требуется найти позицию первого вхождения строки <tex>p</tex> в <tex>s</tex>. | ||
+ | }} | ||
+ | Построим суффиксный автомат для строки <tex>s</tex>.<br> | ||
+ | В процессе построения для каждого состояния <tex>q</tex> будем хранить значение <tex>\mathrm{first(q)}</tex> {{---}} позицию окончания первого вхождения строки. | ||
+ | Поддерживать позицию <tex>\mathrm{first}</tex> можно следующим образом: при добавлении нового состояния <tex>\mathrm{first(cur)} = \mathrm{len(cur)} - 1</tex>, а при клонировании вершины <tex>\mathrm{first(new)} = \mathrm{first(q)}</tex>.<br> | ||
+ | Для поиска вхождения обойдём автомат, как в предыдущей задаче. Пусть состояние <tex>p'</tex> в автомате соответствует строке <tex>p</tex>. Тогда ответ на задачу <tex>\mathrm{first(p')} - |p| + 1</tex>. | ||
+ | <br>Асимптотика {{---}} построение суфавтомата за <tex>O(|s|)</tex>, проверка за <tex>O(|p|)</tex>. | ||
+ | ===Количество различных подстрок=== | ||
+ | {{Задача | ||
+ | |definition = | ||
+ | Дана строка <tex>s</tex>. Найти количество различных подстрок строки <tex>s</tex>. | ||
+ | }} | ||
+ | Построим суффиксный автомат для строки <tex>s</tex>.<br> | ||
+ | Каждой подстроке в суффиксном автомате соответствует путь, тогда ответ на задачу {{---}} количество различных путей из начальной вершины. Так как суфавтомат представляет собой ациклический граф, мы можем найти [[Задача_о_числе_путей_в_ациклическом_графе|количество путей в графе методом динамического программирования]]. | ||
+ | ===Наибольшая общая подстрока двух строк=== | ||
+ | {{Задача | ||
+ | |definition= | ||
+ | Даны строки <tex>s</tex> и <tex>t</tex>. Требуется найти наибольшую общую подстроку двух строк. | ||
+ | }} | ||
+ | Построим суффиксный автомат для строки <tex>s</tex>.<br> | ||
+ | Пройдём по строке <tex>t</tex> и для текущего символа будем искать длину наибольшей общей подстроки, которая заканчивается в текущей позиции. Для этого будем поддерживать <tex>v</tex> {{---}} текущее состояние и <tex>l</tex> {{---}} текущую длину совпадающей части.<br> | ||
+ | Изначально <tex>v = 0</tex>, <tex>l = 0</tex> {{---}} совпадение пустое. Рассматриваем текущий символ <tex>t_i</tex>. Если в автомате существует переход из текущего состояния по данному символу, то перейдем в новое состояние и увеличим длину <tex>l</tex> на <tex>1</tex>.<br>Если перехода не существует, то попробуем минимально уменьшить длину совпадающей подстроки: перейдем по суффиксной ссылке из <tex>v</tex> в новое состояние и примем <tex>l = \mathrm{len(v)}</tex>. Повторим операцию до тех пор, пока не найдём переход. Если по суффиксным ссылкам мы дошли до состояния, в которое ведёт суффиксная ссылка начальной вершины, то это значит, что символа <tex>t_i</tex> нет в строке <tex>s</tex>. В таком случае примем <tex>v = l = 0</tex>, после чего перейдем к следующему символу строки <tex>t</tex>.<br> | ||
+ | Длиной наибольшей общей подстроки будет <tex>\mathrm{maxLen}</tex> {{---}} максимум из всех значений <tex>l</tex>, полученных в ходе работы алгоритма. Тогда ответом на задачу будет являться подстрока <tex>t[(\mathrm{maxPos} - \mathrm{maxLen} + 1) .. \mathrm{maxPos}]</tex>, где <tex>\mathrm{maxPos}</tex> {{---}} позиция, в которой достигнут максимум. | ||
+ | |||
+ | ==Сравнение с другими суффиксными структурами== | ||
+ | Пусть <tex>s</tex> {{---}} строка, для которой строим соответствующую структуру, <tex>n = |s|</tex>, <tex>\Sigma</tex> {{---}} [[Основные_определения:_алфавит,_слово,_язык,_конкатенация,_свободный_моноид_слов;_операции_над_языками|алфавит]]. | ||
+ | |||
+ | {| class="wikitable" | ||
+ | |- | ||
+ | | | ||
+ | | align="center" | '''Время работы''' | ||
+ | | align="center" | '''Память''' | ||
+ | |- | ||
+ | | align="center" | [[Суффиксный бор]] | ||
+ | | align="center" | <tex>O(n ^ 2)</tex> | ||
+ | | align="center" | <tex>O(n^2 + n |\Sigma|)</tex> | ||
+ | |- | ||
+ | | align="center" | [[Сжатое суффиксное дерево]] | ||
+ | | align="center" | <tex>O(n \log{|\Sigma|})</tex> | ||
+ | | align="center" | <tex>O(n |\Sigma|)</tex> | ||
+ | |- | ||
+ | | align="center" | [[Суффиксный массив]] | ||
+ | | align="center" | <tex>O((n + |\Sigma|) \log{n})</tex> | ||
+ | | align="center" | <tex>O(n + |\Sigma|)</tex> | ||
+ | |- | ||
+ | | align="center" | Суффиксный автомат | ||
+ | | align="center" | <tex>O(n \log{|\Sigma|})</tex> | ||
+ | | align="center" | <tex>O(n)</tex> | ||
+ | |} | ||
+ | |||
+ | ==См. также== | ||
+ | * [[Автомат для поиска образца в тексте]] | ||
+ | * [[Алгоритм Ахо-Корасик]] | ||
+ | * [[Поиск наибольшей общей подстроки двух строк с использованием хеширования]] | ||
==Источники информации== | ==Источники информации== | ||
* Maxime Crochemore, Christophe Hancart, Thierry Lecroq {{---}} Algorithms on Strings | * Maxime Crochemore, Christophe Hancart, Thierry Lecroq {{---}} Algorithms on Strings | ||
− | * [http://codeforces.com/blog/entry/22420 | + | * [http://codeforces.com/blog/entry/22420 А. Кульков {{---}} Лекция по суффиксным структурам] |
* [http://e-maxx.ru/algo/suffix_automata MAXimal :: algo :: Суффиксный автомат] | * [http://e-maxx.ru/algo/suffix_automata MAXimal :: algo :: Суффиксный автомат] | ||
[[Категория: Алгоритмы и структуры данных]] | [[Категория: Алгоритмы и структуры данных]] | ||
+ | [[Категория: Структуры данных]] | ||
[[Категория: Поиск подстроки в строке]] | [[Категория: Поиск подстроки в строке]] | ||
+ | [[Категория: Точный поиск]] | ||
+ | [[Категория: Автоматы и регулярные языки]] |
Текущая версия на 19:34, 4 сентября 2022
Определение: |
Суффиксный автомат (англ. suffix automaton, directed acyclic word graph) — минимальный ДКА, который принимает все суффиксы строки и только их. |
Описание
Рассмотрим конечный алфавит
. Пусть — набор слов в алфавите . Суффиксный автомат — это набор , где- — конечный набор состояний,
- — начальное состояние,
- — набор терминальных состояний,
- — функция перехода.
Для
и , определена, если состояние достижимо из переходом по символу . Функция перехода распространяется на слова и обозначает, что если она существует, то состояние достигнуто после чтения слова из состояния . Автомат распознает язык .Суффиксный автомат ациклический ориентированный граф, с начальной вершиной и множеством терминальных вершин, рёбра которого помечены символами :
для строки представляет собой- вершины этого графа — состояния автомата, а рёбра — переходы,
- каждый переход в автомате — ребро в графе, помеченное некоторым символом и все рёбра, исходящие из одной вершины имеют разные метки,
- одно из состояний называется начальным, из него достижимы все остальные состояния,
- одно или несколько состояний помечены как терминальные — если пройти от начального состояния до терминального по какому-либо пути и выписывать при этом символы на переходах, то получим один из суффиксов строки .
Определение: |
Состояние | принимает строку , если существует путь из начального состояния в , такой, что если последовательно выписать буквы на рёбрах на пути, получим строку .
Определение: |
Автомат принимает строку | , если её принимает хотя бы одно из финальных состояний.
Так как автомат детерминированный, то каждому пути соответствует строка.
Если две строки
и принимаются одним состоянием произвольного автомата, то для любой строки строки и принимаются или не принимаются автоматом одновременно. Действительно, независимо от того, как мы пришли в состояние , если мы пройдём из него по пути, соответствующему строке , мы сможем точно сказать, в какое состояние мы попадём (в частности, будет ли оно финальным). Значит, любому состоянию соответствует множество строк , которые переводят его в одно из конечных состояний.Определение: |
Множество | называют правым контекстом состояния.
Правый контекст определен не только для состояния, но и для строк, которые оно принимает — правый контекст строк совпадает с правым контекстом состояния.
Утверждение: |
Состояний в автомате не меньше, чем различных правых контекстов у подстрок строки, для которой он построен, причём в минимальном автомате достигается нижняя оценка. |
Допустим, что в автомате есть два состояния | и такие что . Мы можем удалить состояние и перевести переходы, ведущие в него в состояние . Множество принимаемых строк от этого не изменится, следовательно, мы можем продолжать, пока количество состояний не будет равно числу попарно различных правых контекстов.
Таким образом, ДКА является минимальным тогда и только тогда, когда правые контексты всех его состояний попарно различны. В случае суффиксного автомата правый контекст
строки взаимнооднозначно соответствует множеству правых позиций вхождений строки в строку . Таким образом, каждое состояние автомата принимает строки с одинаковым множеством правых позиций их вхождений и обратно, все строки с таким множеством позиций принимается этим состоянием.Построение
Алгоритм
Определение: |
Пусть длина самой короткой строки, которая принимается состоянием | равно , тогда суффиксная ссылка будет вести из этого состояния в состояние, которое принимает эту же строку без первого символа.
Будем обозначать длину самой длинной строки, которая принимается состоянием
Обозначим состояние , соответствующее текущей строке до добавления символа (изначально ).
Создадим новое состояние , .
Рассмотрим все переходы из по текущему символу . Если перехода нет, то добавляем переход в , переходим по суффиксной ссылке и повторяем процедуру снова. Если переход существует, то остановимся и обозначим текущее состояние за . Если перехода не нашлось и по суффиксным ссылкам мы дошли до фиктивного состояния (на которое указывает ), то .
Допустим, что мы остановились в состоянии , из которого существует переход с символом . Обозначим состояние, куда ведёт переход, через . Рассмотрим два случая:
- Если
- В противном случае, создадим новое состояние , скопируем в него вместе с суффиксными ссылками и переходами. присвоим значение . Перенаправим суффиксную ссылку из в и добавим ссылку из в . Пройдём по всем суффиксным ссылкам из состояния и все переходы в состояние по символу перенаправим в .
Обновим значение
.Пример построения
Изображение | Описание |
---|---|
Изначально автомат состоит из одного начального состояния. | |
Добавляем символ | . Создаем состояние . Переходов из начального состояния по символу нет, перейти по суффиксным ссылкам некуда, значит добавим суффиксную ссылку .|
Добавляем символ | . Создаем состояние . Добавим переход из , откатимся по суффиксной ссылке и добавим переход из . Добавим суффиксную ссылку .|
Аналогично добавим символ | и обновим автомат.|
Добавляем символ | . Добавим переход из и перейдем по суффиксной ссылке в начальное состояние. Из состояния существует переход по символу|
Рассмотрим состояние
| , куда существует переход. Имеем .
|
Построение автомата завершено. Чтобы пометить терминальные вершины, найдём состояние, которое принимает строку | и пройдём по суффиксным ссылкам, помечая все посещенные состояния терминальными.
Реализация
В приведённой ниже реализации используются следующие переменные:
- — массив отображений (ключ — символ, значение — номер состояния) с переходами,
- — массив суффиксных ссылок,
- — массив длин строк,
- — функция, которая создаёт новое состояние и возвращает его номер,
- — функция, которая копирует состояние и возвращает номер нового состояния,
- — последнее состояние.
func addChar(c : char):
int cur = newState() // создаём новое состояние и получаем его номер
int p = last
while p >= 0 and edges[p].find(c) == null
edges[p][c] = cur
p = link[p]
if p != -1
int q = edges[p][c]
if len[p] + 1 == len[q]
link[cur] = q
else
int new = clone(q) // скопируем состояние
и получим номер нового состояния
len[new] = len[p] + 1
link[q] = link[cur] = new
while p >= 0 and edges[p][c] == q
edges[p][c] = new
p = link[p]
last = cur
Применение
Проверка вхождения строки
Задача: |
Даны строки | и . Требуется проверить, является ли строка подстрокой .
Построим суффиксный автомат для строки
Пусть текущее состояние — , изначально равно (начальному состоянию).
Будем по очереди обрабатывать символы строки . Если из состояния есть переход в по текущему символу, то перейдем в новое состояние и повторим процедуру. Если перехода не существует, то не является подстрокой . Если успешно обработали все символы , то она является подстрокой .
Асимптотика — построение суфавтомата за , проверка за .
Позиция первого вхождения строки
Задача: |
Даны строки | и . Требуется найти позицию первого вхождения строки в .
Построим суффиксный автомат для строки
В процессе построения для каждого состояния будем хранить значение — позицию окончания первого вхождения строки.
Поддерживать позицию можно следующим образом: при добавлении нового состояния , а при клонировании вершины .
Для поиска вхождения обойдём автомат, как в предыдущей задаче. Пусть состояние в автомате соответствует строке . Тогда ответ на задачу .
Асимптотика — построение суфавтомата за , проверка за .
Количество различных подстрок
Задача: |
Дана строка | . Найти количество различных подстрок строки .
Построим суффиксный автомат для строки
Каждой подстроке в суффиксном автомате соответствует путь, тогда ответ на задачу — количество различных путей из начальной вершины. Так как суфавтомат представляет собой ациклический граф, мы можем найти количество путей в графе методом динамического программирования.
Наибольшая общая подстрока двух строк
Задача: |
Даны строки | и . Требуется найти наибольшую общую подстроку двух строк.
Построим суффиксный автомат для строки
Пройдём по строке и для текущего символа будем искать длину наибольшей общей подстроки, которая заканчивается в текущей позиции. Для этого будем поддерживать — текущее состояние и — текущую длину совпадающей части.
Изначально , — совпадение пустое. Рассматриваем текущий символ . Если в автомате существует переход из текущего состояния по данному символу, то перейдем в новое состояние и увеличим длину на .
Если перехода не существует, то попробуем минимально уменьшить длину совпадающей подстроки: перейдем по суффиксной ссылке из в новое состояние и примем . Повторим операцию до тех пор, пока не найдём переход. Если по суффиксным ссылкам мы дошли до состояния, в которое ведёт суффиксная ссылка начальной вершины, то это значит, что символа нет в строке . В таком случае примем , после чего перейдем к следующему символу строки .
Длиной наибольшей общей подстроки будет — максимум из всех значений , полученных в ходе работы алгоритма. Тогда ответом на задачу будет являться подстрока , где — позиция, в которой достигнут максимум.
Сравнение с другими суффиксными структурами
Пусть алфавит.
— строка, для которой строим соответствующую структуру, , —Время работы | Память | |
Суффиксный бор | ||
Сжатое суффиксное дерево | ||
Суффиксный массив | ||
Суффиксный автомат |
См. также
Источники информации
- Maxime Crochemore, Christophe Hancart, Thierry Lecroq — Algorithms on Strings
- А. Кульков — Лекция по суффиксным структурам
- MAXimal :: algo :: Суффиксный автомат