Суффиксный автомат — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Алгоритм)
 
(не показаны 23 промежуточные версии 7 участников)
Строка 6: Строка 6:
 
__TOC__
 
__TOC__
 
==Описание==
 
==Описание==
Детерминированным конечным автоматом называется пятёрка (<tex>S, s, \Sigma, \delta, T)</tex>), где
+
Рассмотрим конечный алфавит <tex>A</tex>. Пусть <tex>A^*</tex> {{---}} набор слов в алфавите <tex>A</tex>. Суффиксный автомат <tex>\mathcal{A}</tex> {{---}} это набор <tex>\langle Q, A, i, T, \delta \rangle</tex>, где
* <tex>S</tex> {{---}} множество состояний,
+
* <tex>Q</tex> {{---}} конечный набор состояний,
* <tex>s \in S</tex> {{---}} начальное состояние,
+
* <tex>i</tex> {{---}} начальное состояние,
* <tex>\Sigma</tex> {{---}} конечный алфавит,
+
* <tex>T</tex> {{---}} набор терминальных состояний,
* <tex>\delta : S \times \Sigma \to S</tex> {{---}} функция переходов,
+
* <tex>\delta</tex> {{---}} функция перехода.
* <tex>T \subset S</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>A</tex> для строки <tex>s</tex> представляет собой ациклический ориентированный граф, с начальной вершиной и множеством терминальных вершин, рёбра которого помечены символами <tex>s</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>.]]
  
Строка 42: Строка 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>. Таким образом, каждое состояние автомата принимает строки с одинаковым множеством правых позиций их вхождений и обратно, все строки с таким множеством позиций принимается этим состоянием.
  
 
==Построение==
 
==Построение==
Строка 48: Строка 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(q) = cur</tex>.<br>
+
# Если <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>.
 +
 
 
===Пример построения===
 
===Пример построения===
Рассмотрим построение суффиксного автомата для строки <tex>abcb</tex>. Серыми стрелками показаны суффиксные ссылки.
+
{| class = "wikitable"
# Изначально автомат состоит из одного начального состояния. <tex>last = 0, len(0) = 0</tex>.<br><br>[[Файл:Suffix_automaton_s1.png|x180px|thumb|center|Шаг 1.]]<br>
+
! Изображение !! Описание
# Добавляем символ <tex>a</tex>. Создаем состояние <tex>1</tex>. Переходов из начального состояния по символу <tex>a</tex> нет, перейти по суффиксным ссылкам некуда, значит добавим суффиксную ссылку <tex>link_{1} = 0</tex>. <tex>last = 1, len(1) = 1</tex>.<br><br>[[Файл:Suffix_automaton_s2.png|x180px|thumb|center|Шаг 2.]]<br>
+
|-
# Добавляем символ <tex>b</tex>. Создаем состояние <tex>2</tex>. Добавим переход из <tex>1</tex>, откатимся по суффиксной ссылке и добавим переход из <tex>0</tex>. Добавим суффиксную ссылку <tex>link_{2} = 0</tex>. <tex>last = 2, len(2) = 2</tex>.<br><br>[[Файл:Suffix_automaton_s3.png|x180px|thumb|center|Шаг 3.]]<br>
+
|style="background-color:#FFF" |[[Файл:Suffix_automaton_s1.png|x180px|center|Шаг 1.]]
# Аналогично добавим символ <tex>c</tex> и обновим автомат. <tex>last = 3, len(3) = 3</tex>.<br><br>[[Файл:Suffix_automaton_s4.png|x180px|thumb|center|Шаг 4.]]<br>
+
|Изначально автомат состоит из одного начального состояния. <tex>\mathrm{last} = 0, \mathrm{len(0)} = 0</tex>
# Добавляем символ <tex>b</tex>. Добавим переход из <tex>3</tex> и перейдем по суффиксной ссылке в начальное состояние. Из состояния <tex>0</tex> существует переход по символу <tex>b</tex>.<br><br>[[Файл:Suffix_automaton_s5.png|x180px|thumb|center|Шаг 5.]]<br>
+
|-
# Рассмотрим состояние <tex>2</tex>, куда существует переход. Имеем <tex>len(0) + 1 \neq len(2)</tex>.
+
|style="background-color:#FFF" |[[Файл:Suffix_automaton_s2.png|x180px|center|Шаг 2.]]
## Создаем новое состояние <tex>5</tex>.
+
|Добавляем символ <tex>a</tex>. Создаем состояние <tex>1</tex>. Переходов из начального состояния по символу <tex>a</tex> нет, перейти по суффиксным ссылкам некуда, значит добавим суффиксную ссылку <tex>\mathrm{link_{1}} = 0</tex>. <tex>\mathrm{last} = 1, \mathrm{len(1)} = 1</tex>
## Копируем в него все суффиксные ссылки и переходы из <tex>2</tex> и присвоим <tex>len(5) = 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>.<br><br>[[Файл:Suffix_automaton_s6.png|x180px|thumb|center|Шаг 6.]]<br>
+
|style="background-color:#FFF" |[[Файл:Suffix_automaton_s3.png|x180px|center|Шаг 3.]]
# Построение автомата завершено. Чтобы пометить терминальные вершины, найдём состояние, которое принимает строку <tex>abcb</tex> и пройдём по суффиксным ссылкам, помечая все посещенные состояния терминальными.<br><br>[[Файл:Suffix_automaton_s7.png|x180px|thumb|center|Шаг 7.]]
+
|Добавляем символ <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>edges</tex>,
+
В приведённой ниже реализации используются следующие переменные:
* Суффиксные ссылки хранятся в массиве <tex>link</tex>,  
+
* <tex>\mathrm{edges[]}</tex> {{---}} массив отображений (ключ {{---}} символ, значение {{---}} номер состояния) с переходами,
* Длины строк хранятся в массиве <tex>len</tex>,
+
* <tex>\mathrm{link[]}</tex> {{---}} массив суффиксных ссылок,  
* Функция <tex>newState</tex> создаёт новое состояние и возвращает его номер,
+
* <tex>\mathrm{len[]}</tex> {{---}} массив длин строк,
* Функция <tex>clone</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">// создаём новое состояние и возвращаем его номер</font>  
+
     '''int''' cur = newState()                                      <font color="green">// создаём новое состояние и получаем его номер</font>  
 
   
 
   
     p = last
+
     '''int''' p = last
     '''while''' p >= 0 '''and''' edges[p].find(c) == ''null''''':'''
+
     '''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
        q = edges[p][c]
+
        '''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]
Строка 106: Строка 131:
 
}}
 
}}
 
Построим суффиксный автомат для строки <tex>s</tex>.<br>
 
Построим суффиксный автомат для строки <tex>s</tex>.<br>
Пусть текущее состояние {{---}} <tex>cur</tex>, изначально равно <tex>0</tex> (начальному состоянию).<br>
+
Пусть текущее состояние {{---}} <tex>\mathrm{cur}</tex>, изначально равно <tex>0</tex> (начальному состоянию).<br>
Будем по очереди обрабатывать символы строки <tex>p</tex>. Если из состояния <tex>cur</tex> есть переход в по текущему символу, но перейдем в новое состояние и повторим процедуру. Если перехода не существует, то <tex>p</tex> не является подстрокой <tex>s</tex>. Если успешно обработали все символы <tex>p</tex>, то она является подстрокой <tex>s</tex>.<br>Асимптотика {{---}} построение суфавтомата за <tex>O(|s|)</tex>, проверка за <tex>O(|p|)</tex>.
+
Будем по очереди обрабатывать символы строки <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>.
  
 
===Позиция первого вхождения строки===
 
===Позиция первого вхождения строки===
Строка 115: Строка 140:
 
}}
 
}}
 
Построим суффиксный автомат для строки <tex>s</tex>.<br>
 
Построим суффиксный автомат для строки <tex>s</tex>.<br>
В процессе построения для каждого состояния <tex>q</tex> будем хранить значение <tex>first(q)</tex> {{---}} позицию окончания первого вхождения строки.
+
В процессе построения для каждого состояния <tex>q</tex> будем хранить значение <tex>\mathrm{first(q)}</tex> {{---}} позицию окончания первого вхождения строки.
Поддерживать позицию <tex>first</tex> можно следующим образом: при добавлении нового состояния <tex>first(cur) = len(cur) - 1</tex>, а при клонировании вершины <tex>first(new) = first(q)</tex>.<br>
+
Поддерживать позицию <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>first(p') - |p| + 1</tex>.
+
Для поиска вхождения обойдём автомат, как в предыдущей задаче. Пусть состояние <tex>p'</tex> в автомате соответствует строке <tex>p</tex>. Тогда ответ на задачу <tex>\mathrm{first(p')} - |p| + 1</tex>.
 
<br>Асимптотика {{---}} построение суфавтомата за <tex>O(|s|)</tex>, проверка за <tex>O(|p|)</tex>.
 
<br>Асимптотика {{---}} построение суфавтомата за <tex>O(|s|)</tex>, проверка за <tex>O(|p|)</tex>.
 
===Количество различных подстрок===
 
===Количество различных подстрок===
Строка 126: Строка 151:
 
Построим суффиксный автомат для строки <tex>s</tex>.<br>
 
Построим суффиксный автомат для строки <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> {{---}} позиция, в которой достигнут максимум.
  
 
==Сравнение с другими суффиксными структурами==
 
==Сравнение с другими суффиксными структурами==
Строка 152: Строка 186:
 
| align="center" | <tex>O(n)</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 :: Суффиксный автомат]
  
 
[[Категория: Алгоритмы и структуры данных]]
 
[[Категория: Алгоритмы и структуры данных]]
 +
[[Категория: Структуры данных]]
 
[[Категория: Поиск подстроки в строке]]
 
[[Категория: Поиск подстроки в строке]]
 +
[[Категория: Точный поиск]]
 +
[[Категория: Автоматы и регулярные языки]]

Текущая версия на 01:38, 30 августа 2018

Определение:
Суффиксный автомат (англ. suffix automaton, directed acyclic word graph) — минимальный ДКА, который принимает все суффиксы строки и только их.


Описание[править]

Рассмотрим конечный алфавит [math]A[/math]. Пусть [math]A^*[/math] — набор слов в алфавите [math]A[/math]. Суффиксный автомат [math]\mathcal{A}[/math] — это набор [math]\langle Q, A, i, T, \delta \rangle[/math], где

  • [math]Q[/math] — конечный набор состояний,
  • [math]i[/math] — начальное состояние,
  • [math]T[/math] — набор терминальных состояний,
  • [math]\delta[/math] — функция перехода.

Для [math]q \in Q[/math] и [math]a \in A[/math], [math]\delta(q, a)[/math] определена, если состояние достижимо из [math]q[/math] переходом по символу [math]a[/math]. Функция перехода распространяется на слова и [math]\delta(q, x)[/math] обозначает, что если она существует, то состояние достигнуто после чтения слова [math]x[/math] из состояния [math]q[/math]. Автомат [math]\mathcal{A}[/math] распознает язык [math]\{x \in A^* : \delta(i, x) \in T \}[/math].

Суффиксный автомат [math]\mathcal{A}[/math] для строки [math]s[/math] представляет собой ациклический ориентированный граф, с начальной вершиной и множеством терминальных вершин, рёбра которого помечены символами [math]s[/math]:

  • вершины этого графа — состояния автомата, а рёбра — переходы,
  • каждый переход в автомате — ребро в графе, помеченное некоторым символом и все рёбра, исходящие из одной вершины имеют разные метки,
  • одно из состояний называется начальным, из него достижимы все остальные состояния,
  • одно или несколько состояний помечены как терминальные — если пройти от начального состояния до терминального по какому-либо пути и выписывать при этом символы на переходах, то получим один из суффиксов строки [math]s[/math].


Пример суффиксного автомата для строки [math]abbab[/math].


Определение:
Состояние [math]q[/math] принимает строку [math]s[/math], если существует путь из начального состояния в [math]q[/math], такой, что если последовательно выписать буквы на рёбрах на пути, получим строку [math]s[/math].


Определение:
Автомат принимает строку [math]s[/math], если её принимает хотя бы одно из финальных состояний.


Так как автомат детерминированный, то каждому пути соответствует строка.

Если две строки [math]a[/math] и [math]b[/math] принимаются одним состоянием [math]q[/math] произвольного автомата, то для любой строки [math]x[/math] строки [math]ax[/math] и [math]bx[/math] принимаются или не принимаются автоматом одновременно. Действительно, независимо от того, как мы пришли в состояние [math]q[/math], если мы пройдём из него по пути, соответствующему строке [math]x[/math], мы сможем точно сказать, в какое состояние мы попадём (в частности, будет ли оно финальным). Значит, любому состоянию [math]q[/math] соответствует множество строк [math]X_q[/math], которые переводят его в одно из конечных состояний.

Определение:
Множество [math]X_q[/math] называют правым контекстом состояния.


Правый контекст определен не только для состояния, но и для строк, которые оно принимает — правый контекст строк совпадает с правым контекстом состояния.

Утверждение:
Состояний в автомате не меньше, чем различных правых контекстов у подстрок строки, для которой он построен, причём в минимальном автомате достигается нижняя оценка.
[math]\triangleright[/math]
Допустим, что в автомате есть два состояния [math]q_1[/math] и [math]q_2[/math] такие что [math]X_{q_1} = X_{q_2}[/math]. Мы можем удалить состояние [math]q_2[/math] и перевести переходы, ведущие в него в состояние [math]q_1[/math]. Множество принимаемых строк от этого не изменится, следовательно, мы можем продолжать, пока количество состояний не будет равно числу попарно различных правых контекстов.
[math]\triangleleft[/math]

Таким образом, ДКА является минимальным тогда и только тогда, когда правые контексты всех его состояний попарно различны. В случае суффиксного автомата правый контекст [math]X_a[/math] строки [math]a[/math] взаимнооднозначно соответствует множеству правых позиций вхождений строки [math]a[/math] в строку [math]s[/math]. Таким образом, каждое состояние автомата принимает строки с одинаковым множеством правых позиций их вхождений и обратно, все строки с таким множеством позиций принимается этим состоянием.

Построение[править]

Алгоритм[править]

Определение:
Пусть длина самой короткой строки, которая принимается состоянием [math]q[/math] равно [math]k[/math], тогда суффиксная ссылка [math]\mathrm{link_q}[/math] будет вести из этого состояния в состояние, которое принимает эту же строку без первого символа.

Будем обозначать длину самой длинной строки, которая принимается состоянием [math]q[/math] как [math]\mathrm{len_q}[/math]. Длина самой короткой строки из [math]q[/math] в таком случае будет равна [math]\mathrm{len(link_q)} + 1[/math]. Суффиксный автомат может быть построен за линейное время (при константном размере алфавита) online-алгоритмом. Будем добавлять символы строки [math]s[/math] по одному, перестраивая при этом автомат. Изначально автомат состоит из одного состояния, для которого [math]\mathrm{len(0)} = 0[/math], а [math]\mathrm{link_0} = -1[/math].
Обозначим состояние [math]\mathrm{last}[/math], соответствующее текущей строке до добавления символа [math]c[/math] (изначально [math]\mathrm{last} = 0[/math]).
Создадим новое состояние [math]\mathrm{cur}[/math], [math]\mathrm{len(cur)} = \mathrm{len(last)} + 1[/math].
Рассмотрим все переходы из [math]\mathrm{last}[/math] по текущему символу [math]c[/math]. Если перехода нет, то добавляем переход в [math]\mathrm{cur}[/math], переходим по суффиксной ссылке и повторяем процедуру снова. Если переход существует, то остановимся и обозначим текущее состояние за [math]p[/math]. Если перехода не нашлось и по суффиксным ссылкам мы дошли до фиктивного состояния (на которое указывает [math]\mathrm{link_0}[/math]), то [math]\mathrm{link_{cur}} = 0[/math].
Допустим, что мы остановились в состоянии [math]p[/math], из которого существует переход с символом [math]c[/math]. Обозначим состояние, куда ведёт переход, через [math]q[/math]. Рассмотрим два случая:

  1. Если [math]\mathrm{len(p)} + 1 = \mathrm{len(q)}[/math], то [math]\mathrm{link(cur)} = \mathrm{q}[/math].
  2. В противном случае, создадим новое состояние [math]\mathrm{new}[/math], скопируем в него [math]q[/math] вместе с суффиксными ссылками и переходами. [math]\mathrm{len(new)}[/math] присвоим значение [math]\mathrm{len(p)} + 1[/math]. Перенаправим суффиксную ссылку из [math]q[/math] в [math]\mathrm{new}[/math] и добавим ссылку из [math]\mathrm{cur}[/math] в [math]\mathrm{new}[/math]. Пройдём по всем суффиксным ссылкам из состояния [math]p[/math] и все переходы в состояние [math]q[/math] по символу [math]c[/math] перенаправим в [math]\mathrm{new}[/math].

Обновим значение [math]\mathrm{last} = \mathrm{cur}[/math].

Пример построения[править]

Изображение Описание
Шаг 1.
Изначально автомат состоит из одного начального состояния. [math]\mathrm{last} = 0, \mathrm{len(0)} = 0[/math]
Шаг 2.
Добавляем символ [math]a[/math]. Создаем состояние [math]1[/math]. Переходов из начального состояния по символу [math]a[/math] нет, перейти по суффиксным ссылкам некуда, значит добавим суффиксную ссылку [math]\mathrm{link_{1}} = 0[/math]. [math]\mathrm{last} = 1, \mathrm{len(1)} = 1[/math]
Шаг 3.
Добавляем символ [math]b[/math]. Создаем состояние [math]2[/math]. Добавим переход из [math]1[/math], откатимся по суффиксной ссылке и добавим переход из [math]0[/math]. Добавим суффиксную ссылку [math]\mathrm{link_{2}} = 0[/math]. [math]\mathrm{last} = 2, \mathrm{len(2)} = 2[/math]
Шаг 4.
Аналогично добавим символ [math]c[/math] и обновим автомат. [math]\mathrm{last} = 3, \mathrm{len(3)} = 3[/math]
Шаг 5.
Добавляем символ [math]b[/math]. Добавим переход из [math]3[/math] и перейдем по суффиксной ссылке в начальное состояние. Из состояния [math]0[/math] существует переход по символу [math]b[/math]
Шаг 6.
Рассмотрим состояние [math]2[/math], куда существует переход. Имеем [math]\mathrm{len(0)} + 1 \neq \mathrm{len(2)}[/math].
  1. Создаем новое состояние [math]5[/math].
  2. Копируем в него все суффиксные ссылки и переходы из [math]2[/math] и присвоим [math]\mathrm{len(5)} = \mathrm{len(0)} + 1 = 1[/math].
  3. Перенаправим суффиксную ссылку из [math]2[/math] в [math]5[/math] и добавим ссылку из [math]4[/math] в [math]5[/math]. Перенаправим переход [math]0 \rightarrow 2[/math] в состояние [math]5[/math].
Шаг 7.
Построение автомата завершено. Чтобы пометить терминальные вершины, найдём состояние, которое принимает строку [math]abcb[/math] и пройдём по суффиксным ссылкам, помечая все посещенные состояния терминальными.

Реализация[править]

В приведённой ниже реализации используются следующие переменные:

  • [math]\mathrm{edges[]}[/math] — массив отображений (ключ — символ, значение — номер состояния) с переходами,
  • [math]\mathrm{link[]}[/math] — массив суффиксных ссылок,
  • [math]\mathrm{len[]}[/math] — массив длин строк,
  • [math]\mathrm{newState()}[/math] — функция, которая создаёт новое состояние и возвращает его номер,
  • [math]\mathrm{clone()}[/math] — функция, которая копирует состояние и возвращает номер нового состояния,
  • [math]\mathrm{last}[/math] — последнее состояние.
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)                                // скопируем состояние [math]q[/math] и получим номер нового состояния
            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

Применение[править]

Проверка вхождения строки[править]

Задача:
Даны строки [math]s[/math] и [math]p[/math]. Требуется проверить, является ли строка [math]p[/math] подстрокой [math]s[/math].

Построим суффиксный автомат для строки [math]s[/math].
Пусть текущее состояние — [math]\mathrm{cur}[/math], изначально равно [math]0[/math] (начальному состоянию).
Будем по очереди обрабатывать символы строки [math]p[/math]. Если из состояния [math]\mathrm{cur}[/math] есть переход в по текущему символу, то перейдем в новое состояние и повторим процедуру. Если перехода не существует, то [math]p[/math] не является подстрокой [math]s[/math]. Если успешно обработали все символы [math]p[/math], то она является подстрокой [math]s[/math].
Асимптотика — построение суфавтомата за [math]O(|s|)[/math], проверка за [math]O(|p|)[/math].

Позиция первого вхождения строки[править]

Задача:
Даны строки [math]s[/math] и [math]p[/math]. Требуется найти позицию первого вхождения строки [math]p[/math] в [math]s[/math].

Построим суффиксный автомат для строки [math]s[/math].
В процессе построения для каждого состояния [math]q[/math] будем хранить значение [math]\mathrm{first(q)}[/math] — позицию окончания первого вхождения строки. Поддерживать позицию [math]\mathrm{first}[/math] можно следующим образом: при добавлении нового состояния [math]\mathrm{first(cur)} = \mathrm{len(cur)} - 1[/math], а при клонировании вершины [math]\mathrm{first(new)} = \mathrm{first(q)}[/math].
Для поиска вхождения обойдём автомат, как в предыдущей задаче. Пусть состояние [math]p'[/math] в автомате соответствует строке [math]p[/math]. Тогда ответ на задачу [math]\mathrm{first(p')} - |p| + 1[/math].
Асимптотика — построение суфавтомата за [math]O(|s|)[/math], проверка за [math]O(|p|)[/math].

Количество различных подстрок[править]

Задача:
Дана строка [math]s[/math]. Найти количество различных подстрок строки [math]s[/math].

Построим суффиксный автомат для строки [math]s[/math].
Каждой подстроке в суффиксном автомате соответствует путь, тогда ответ на задачу — количество различных путей из начальной вершины. Так как суфавтомат представляет собой ациклический граф, мы можем найти количество путей в графе методом динамического программирования.

Наибольшая общая подстрока двух строк[править]

Задача:
Даны строки [math]s[/math] и [math]t[/math]. Требуется найти наибольшую общую подстроку двух строк.

Построим суффиксный автомат для строки [math]s[/math].
Пройдём по строке [math]t[/math] и для текущего символа будем искать длину наибольшей общей подстроки, которая заканчивается в текущей позиции. Для этого будем поддерживать [math]v[/math] — текущее состояние и [math]l[/math] — текущую длину совпадающей части.
Изначально [math]v = 0[/math], [math]l = 0[/math] — совпадение пустое. Рассматриваем текущий символ [math]t_i[/math]. Если в автомате существует переход из текущего состояния по данному символу, то перейдем в новое состояние и увеличим длину [math]l[/math] на [math]1[/math].
Если перехода не существует, то попробуем минимально уменьшить длину совпадающей подстроки: перейдем по суффиксной ссылке из [math]v[/math] в новое состояние и примем [math]l = \mathrm{len(v)}[/math]. Повторим операцию до тех пор, пока не найдём переход. Если по суффиксным ссылкам мы дошли до состояния, в которое ведёт суффиксная ссылка начальной вершины, то это значит, что символа [math]t_i[/math] нет в строке [math]s[/math]. В таком случае примем [math]v = l = 0[/math], после чего перейдем к следующему символу строки [math]t[/math].
Длиной наибольшей общей подстроки будет [math]\mathrm{maxLen}[/math] — максимум из всех значений [math]l[/math], полученных в ходе работы алгоритма. Тогда ответом на задачу будет являться подстрока [math]t[(\mathrm{maxPos} - \mathrm{maxLen} + 1) .. \mathrm{maxPos}][/math], где [math]\mathrm{maxPos}[/math] — позиция, в которой достигнут максимум.

Сравнение с другими суффиксными структурами[править]

Пусть [math]s[/math] — строка, для которой строим соответствующую структуру, [math]n = |s|[/math], [math]\Sigma[/math]алфавит.

Время работы Память
Суффиксный бор [math]O(n ^ 2)[/math] [math]O(n^2 + n |\Sigma|)[/math]
Сжатое суффиксное дерево [math]O(n \log{|\Sigma|})[/math] [math]O(n |\Sigma|)[/math]
Суффиксный массив [math]O((n + |\Sigma|) \log{n})[/math] [math]O(n + |\Sigma|)[/math]
Суффиксный автомат [math]O(n \log{|\Sigma|})[/math] [math]O(n)[/math]

См. также[править]

Источники информации[править]