<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.204.71.254&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.204.71.254&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/37.204.71.254"/>
		<updated>2026-04-16T04:56:43Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B6%D0%B0%D1%82%D0%BE%D0%B5_%D1%81%D1%83%D1%84%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE&amp;diff=81885</id>
		<title>Сжатое суффиксное дерево</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B6%D0%B0%D1%82%D0%BE%D0%B5_%D1%81%D1%83%D1%84%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE&amp;diff=81885"/>
				<updated>2021-12-25T23:45:50Z</updated>
		
		<summary type="html">&lt;p&gt;37.204.71.254: /* Наивный алгоритм */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Суффиксный бор|Суффиксный бор]] {{---}} удобная структура данных для поиска подстроки в строке, но она требует порядка квадрата длины исходной строки памяти. Оптимизацией суффиксного бора, требующей линейное количество памяти, является '''сжатое суффиксное дерево''' (англ. ''compressed suffix tree''), рассматриваемое далее.&lt;br /&gt;
&lt;br /&gt;
==Определение==&lt;br /&gt;
{{Определение&lt;br /&gt;
|neat = 1&lt;br /&gt;
|id=suffix_tree&lt;br /&gt;
|definition =&lt;br /&gt;
'''Суффиксное дерево''' (сжатое суффиксное дерево) &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; для строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; (где  &amp;lt;tex&amp;gt;|s| = n&amp;lt;/tex&amp;gt;) {{---}} дерево с &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; листьями, обладающее следующими свойствами:&lt;br /&gt;
*каждая внутренняя вершина дерева имеет не меньше двух детей;&lt;br /&gt;
*каждое ребро помечено непустой подстрокой строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;;&lt;br /&gt;
*никакие два ребра, выходящие из одной вершины, не могут иметь пометок, начинающихся с одного и того же символа;&lt;br /&gt;
*дерево должно содержать все суффиксы строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, причем каждый суффикс заканчивается точно в листе и нигде кроме него.&lt;br /&gt;
}} &lt;br /&gt;
[[Файл:Suffix_tree_3.png|250px|thumb|Суффиксное дерево для строки &amp;lt;tex&amp;gt;xabxa&amp;lt;/tex&amp;gt; с защитным символом]] &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Данное определение порождает следующую проблему:''' &amp;lt;br/&amp;gt;&lt;br /&gt;
Рассмотрим дерево для строки &amp;lt;tex&amp;gt;xabxa&amp;lt;/tex&amp;gt;: суффикс &amp;lt;tex&amp;gt;xa&amp;lt;/tex&amp;gt; является префиксом суффикса &amp;lt;tex&amp;gt;xabxa&amp;lt;/tex&amp;gt;, а, значит, этот суффикс не закачивается в листе. Для решения проблемы в конце строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; добавляют символ, не входящий в исходный алфавит: '''''защитный''''' символ. Обозначим его как &amp;lt;tex&amp;gt;\$&amp;lt;/tex&amp;gt;. Любой суффикс строки с защитным символом действительно заканчивается в листе и только в листе, т. к. в такой строке не существует двух различных подстрок одинаковой длины, заканчивающихся на &amp;lt;tex&amp;gt;\$&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; {{---}} длина строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; с защитным символом.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Число вершин==&lt;br /&gt;
По определению, в суффиксном дереве содержится &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; листьев. Оценим число внутренних вершин такого дерева.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=&lt;br /&gt;
Число внутренних вершин дерева, каждая из которых имеет не менее двух детей, меньше числа листьев.&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
: Докажем лемму индукцией по числу листьев &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
: '''База'''&lt;br /&gt;
&lt;br /&gt;
: При &amp;lt;tex&amp;gt;n = 2&amp;lt;/tex&amp;gt; в дереве одна внутренняя вершина, следовательно утверждение верно.&lt;br /&gt;
&lt;br /&gt;
: '''Переход''' &amp;lt;tex&amp;gt;n \rightarrow n + 1&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
: Возьмем вершину в дереве с &amp;lt;tex&amp;gt;n + 1&amp;lt;/tex&amp;gt; листами, у которой два ребенка {{---}} листья. Рассмотрим возможные случаи:&lt;br /&gt;
&lt;br /&gt;
# У нее более двух детей. Тогда отрежем от нее лист. Получим дерево с &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; листьями, причем в нем число внутренних вершин такое же, как в исходном дереве. Но у полученного дерева по индукционному предположению менее &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; внутренних вершин, а, значит, и для исходного дерева лемма верна.&lt;br /&gt;
# У нее ровно два ребенка. Отрежем их, получим дерево с &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; листьями, число внутренних вершин которого на &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; меньше, чем в исходном дереве. Тогда по индукционному предположению у него менее &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; внутренних вершин, значит, в исходном дереве их меньше &amp;lt;tex&amp;gt;n + 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Занимаемая память==&lt;br /&gt;
Представим дерево как двумерный массив размера &amp;lt;tex&amp;gt;|V| \times |\Sigma|&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;|V|&amp;lt;/tex&amp;gt; {{---}} число вершин в дереве, &amp;lt;tex&amp;gt;|\Sigma|&amp;lt;/tex&amp;gt; {{---}} мощность алфавита. Для любого суффиксного дерева верна предыдущая лемма (у каждой вершины, по определению, не менее двух детей), значит, &amp;lt;tex&amp;gt;|V| = O(2 n)&amp;lt;/tex&amp;gt;. Каждая &amp;lt;tex&amp;gt;a[i][j]&amp;lt;/tex&amp;gt; ячейка содержит информацию о том, в какую вершину ведет ребро из &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-ой вершины по &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt;-ому символу и индексы &amp;lt;tex&amp;gt;l, r&amp;lt;/tex&amp;gt; начала и конца подстроки, записанной на данном переходе. Итак, дерево занимает &amp;lt;tex&amp;gt;O(n|\Sigma|)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
==Построение суффиксного дерева==&lt;br /&gt;
&lt;br /&gt;
===Наивный алгоритм===&lt;br /&gt;
Рассмотрим наивный алгоритм построения суффиксного дерева строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 '''struct''' Vertex:  &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// Структура, содержащая информацию о вершине &amp;lt;/span&amp;gt;&lt;br /&gt;
     '''int''' l &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// индекс начала подстроки &amp;lt;/span&amp;gt;&lt;br /&gt;
     '''int''' r &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// индекс конца подстроки &amp;lt;/span&amp;gt;&lt;br /&gt;
     '''int''' v &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// индекс текущей позиции &amp;lt;/span&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
 go[0] = '''new''' Vertex &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// массив из пустых Vertex (можно все поля положить -1), размер массива -- количество символов в алфавите &amp;lt;/span&amp;gt;&lt;br /&gt;
 count = 0        &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// номер последней вершины, созданной в дереве (глобальная переменная)&amp;lt;/span&amp;gt;&lt;br /&gt;
 '''for''' i = 0 '''to''' n   &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// для каждого символа строки&amp;lt;/span&amp;gt;&lt;br /&gt;
     insert(i, n) &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// добавляем суффикс, начинающийся с него&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 '''void''' insert('''int''' l, '''int''' r):&lt;br /&gt;
     cur = 0 &lt;br /&gt;
     '''while''' (l &amp;lt; r)&lt;br /&gt;
         '''if''' (go[cur][s[l]].v == -1)         &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// если мы не можем пойти из вершины по символу &amp;lt;tex&amp;gt; l &amp;lt;/tex&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
             createVertex(cur, l, r)       &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// создаем новую вершину&amp;lt;/span&amp;gt; &lt;br /&gt;
         '''else'''&lt;br /&gt;
             start = go[cur][s[l]].l&lt;br /&gt;
             finish = go[cur][s[l]].r&lt;br /&gt;
             hasCut = ''false''&lt;br /&gt;
             '''for''' j = start '''to''' finish and l + j - start &amp;lt; n      &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// для каждого символа на ребре из текущей вершины&amp;lt;/span&amp;gt;&lt;br /&gt;
                 '''if''' (s[l + j - start] != s[j]) &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// если нашли не совпадающий символ&amp;lt;/span&amp;gt;&lt;br /&gt;
                     &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// создаем вершину на ребре&amp;lt;/span&amp;gt;&lt;br /&gt;
                     old = go[cur][s[l]]&lt;br /&gt;
                     createVertex(cur, l, j)&lt;br /&gt;
                     go[count][s[j]].v = old&lt;br /&gt;
                     go[count][s[j]].l = j&lt;br /&gt;
                     go[count][s[j]].r = finish&lt;br /&gt;
                     createVertex(count, l + j - start, r)&lt;br /&gt;
                     hasCut = ''true''&lt;br /&gt;
                     '''break'''&lt;br /&gt;
             '''if''' (!hasCut)&lt;br /&gt;
                 cur = go[cur][s[l]].v  &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// переходим по ребру&amp;lt;/span&amp;gt;&lt;br /&gt;
                 l = l + finish - start &amp;lt;span style=&amp;quot;color:Green&amp;quot;&amp;gt;// двигаемся по суффиксу на длину подстроки, записанной на ребре&amp;lt;/span&amp;gt;&lt;br /&gt;
             '''else'''&lt;br /&gt;
                 '''break'''&lt;br /&gt;
&lt;br /&gt;
 '''void''' createVertex('''int''' cur, '''int''' l, '''int''' r):&lt;br /&gt;
     go[++count] = '''new''' Vertex&lt;br /&gt;
     go[cur][s[l]].v = count&lt;br /&gt;
     go[cur][s[l]].l = l&lt;br /&gt;
     go[cur][s[l]].r = r&lt;br /&gt;
&lt;br /&gt;
Этот алгоритм работает за время &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;, однако [[Алгоритм Укконена| алгоритм Укконена]] позволяет построить сжатое суффиксное дерево за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Построение из суффиксного массива===&lt;br /&gt;
Пусть нам известен [[Суффиксный массив| суффиксный массив]] &amp;lt;tex&amp;gt;suf&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, его можно получить [[Алгоритм Карккайнена-Сандерса| алгоритмом Карккайнена-Сандерса]] за линейное время. Для преобразования нам также понадобится массив &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt; (''longest common prefix''), который можно получить [[Алгоритм Касаи и др.| алгоритмом Касаи]].&lt;br /&gt;
&lt;br /&gt;
В этом преобразовании используется тот же инвариант, что и в других суффиксных структурах:&lt;br /&gt;
# Строка &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; заканчивается специальным символом, который больше не встречается в строке.&lt;br /&gt;
# Следствие: &amp;lt;tex&amp;gt;lcp[i] &amp;lt; len[i - 1]&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;len[i - 1]&amp;lt;/tex&amp;gt; {{---}} длина суффикса, соответствующего &amp;lt;tex&amp;gt;suf[i - 1]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Будем строить дерево, добавляя суффиксы в лексикографическом порядке. Чтобы ускорить добавление, будем использовать то, что &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-ый суффикс имеет с предыдущим &amp;lt;tex&amp;gt;lcp[i]&amp;lt;/tex&amp;gt; общих символов. Тогда добавление из корня не отличается от того, что мы поднимемся вверх из предыдущего суффикса до глубины &amp;lt;tex&amp;gt;lcp[i]&amp;lt;/tex&amp;gt; и продолжим построение оттуда. Инвариант позволяет нам утверждать, что ни один лист мы не сможем продолжить, и нам всегда нужно будет хоть раз подняться из него вверх. Поскольку суффиксы отсортированы лексикографически, мы не будем спускаться по ребру после того, как уже поднялись из него из-за несовпадения символа. Все это позволяет сформулировать алгоритм добавления суффикса по известной вершине предыдущего суффикса:&lt;br /&gt;
# Подняться из вершины вверх до глубины &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Если эта глубина находится на ребре, разрезать ребро по ней.&lt;br /&gt;
# Вставить новую вершину как сына вершины с глубиной &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В вершинах дерева &amp;lt;tex&amp;gt;Node&amp;lt;/tex&amp;gt; мы будем хранить предка &amp;lt;tex&amp;gt;\mathtt {parent}&amp;lt;/tex&amp;gt;, [[Стек| стек]] детей в лексикографическом порядке ребер &amp;lt;tex&amp;gt;\mathtt{children}&amp;lt;/tex&amp;gt;, глубину вершины в символах от корня &amp;lt;tex&amp;gt;\mathtt{depth}&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Соответственно, конструктор вершины имеет вид &amp;lt;code&amp;gt;Node(Node parent, '''int''' depth)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''Node''' addNextSuffix('''Node''' previous, '''int''' length, '''int''' lcp):&lt;br /&gt;
    '''if''' (previous.depth == 0 '''or''' previous.depth == lcp)          &amp;lt;font color=green&amp;gt; // Добавляем к сыновьям текущей вершины &amp;lt;/font&amp;gt;&lt;br /&gt;
       added = '''Node'''(previous, length)&lt;br /&gt;
       previous.children.push(added)&lt;br /&gt;
       '''return''' added&lt;br /&gt;
    '''else'''&lt;br /&gt;
       '''if''' previous.parent.depth &amp;lt; lcp                          &amp;lt;font color=green&amp;gt; // Нужно разрезать ребро &amp;lt;/font&amp;gt;&lt;br /&gt;
          inserted = '''Node'''(prevous.parent, lcp)&lt;br /&gt;
          previous.parent.children.pop()&lt;br /&gt;
          previous.parent.children.push(inserted)&lt;br /&gt;
          inserted.children.push(previous)&lt;br /&gt;
          previous.parent = inserted&lt;br /&gt;
       '''return''' addNextSuffix(previous.parent, length, lcp)      &lt;br /&gt;
       &lt;br /&gt;
 '''Node''' buildSuffixTree('''int[]''' suf, '''int[]''' lcp, '''int''' length):&lt;br /&gt;
    root = '''Node'''('''null''', 0)&lt;br /&gt;
    previous = root&lt;br /&gt;
    '''for''' i = 1 '''to''' length&lt;br /&gt;
       previous = addNextSuffix(previous, length - suf[i], lcp[i])&lt;br /&gt;
    '''return''' root&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В процессе построения мы нигде не запоминали сами позиции строки, соответствующие ребрам. Чтобы их восстановить, достаточно определить максимальный суффикс, который проходит по этому ребру. Для этого с помощью [[Обход в глубину, цвета вершин| обхода в глубину]] посчитаем для каждой вершину дерева максимальную глубину ее листа &amp;lt;tex&amp;gt;\mathtt{maxDepth}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Тогда ребро &amp;lt;tex&amp;gt;s[start, end]&amp;lt;/tex&amp;gt; определяется так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''void''' calculatePositions('''Node''' parent, '''Node''' child, '''int''' stringLength):&lt;br /&gt;
    start = stringLength - child.maxDepth + parent.depth&lt;br /&gt;
    end = start + child.depth - parent.depth - 1&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для асимптотического анализа будем использовать в качестве [[Амортизационный анализ#Метод потенциалов| потенциала]] глубину в вершинах. При добавлении суффикса мы спускаемся один раз, подняться выше корня мы не можем, значит, и подниматься мы будем суммарно &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; раз. Обход в глубину также выполняется за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;, итоговая асимптотика &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Использование сжатого суффиксного дерева==&lt;br /&gt;
Суффиксное дерево позволяет за линейное время найти:&lt;br /&gt;
# Количество различных подстрок данной строки.&lt;br /&gt;
# Наибольшую общую подстроку двух строк.&lt;br /&gt;
# [[Суффиксный массив| Суффиксный массив]] и массив &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt; (''longest common prefix'') исходной строки.&lt;br /&gt;
# Строку максимальной длины, ветвящуюся влево и вправо за &amp;lt;tex&amp;gt;ST + O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Построение суффиксного массива и массива lcp из суффиксного дерева===&lt;br /&gt;
Пусть к строке дописан специальный символ для сохранения инварианта.&lt;br /&gt;
Рассмотрим лексикографический по ребрам порядок обхода сжатого суффиксного дерева. &lt;br /&gt;
Пусть два суффикса имеют общее начало, но различаются в &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-ом символе. &lt;br /&gt;
Первым будет рассмотрено поддерево по ребру с меньшим символом, значит и лист, соответствующий этому суффиксу, будет посещен первым.&lt;br /&gt;
&lt;br /&gt;
Тогда суффиксный массив строится из суффиксного дерева [[Обход в глубину, цвета вершин| обходом в глубину]] в указанном порядке. &lt;br /&gt;
Пусть длина строки &amp;lt;tex&amp;gt;\mathtt{length}&amp;lt;/tex&amp;gt;, глубина листа в символах &amp;lt;tex&amp;gt;\mathtt{depth}&amp;lt;/tex&amp;gt;, тогда номер суффикса &amp;lt;tex&amp;gt;\mathtt{i = length - depth}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для заполнения массива &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt; нам понадобится вершина &amp;lt;tex&amp;gt;\mathtt{minNode}&amp;lt;/tex&amp;gt;, которая будет означать вершину с минимальной глубиной, в которую мы поднимались при переходе между суффиксами. Поскольку мы точно поднимались туда, но не поднимались выше, это будет [[Сведение задачи LCA к задаче RMQ| наименьший общий предок]] этих узлов. Из этого следует, что у рассматриваемых суффиксов совпадает ровно &amp;lt;tex&amp;gt;\mathtt{lcp = minNode.depth}&amp;lt;/tex&amp;gt; символов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''int''' curPos = 0&lt;br /&gt;
 '''Node''' minNode = root&lt;br /&gt;
 &amp;lt;font color=green&amp;gt;// Для заполнения нужно вызвать dfs(root) &amp;lt;/font&amp;gt;&lt;br /&gt;
 '''void''' dfs('''Node''' n):&lt;br /&gt;
    '''if''' n.children.size == 0&lt;br /&gt;
       suf[curPos] = length - n.depth&lt;br /&gt;
       lcp[curPos] = minNode.depth&lt;br /&gt;
       curPos++&lt;br /&gt;
       minNode = n&lt;br /&gt;
    '''else'''&lt;br /&gt;
       '''foreach''' child '''in''' n.children&lt;br /&gt;
          '''if''' n.depth &amp;lt; minNode.depth:&lt;br /&gt;
             minNode = n&lt;br /&gt;
          dfs(child)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Асимптотика алгоритма совпадает с асимптотикой обхода в глубину и составляет &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, мы умеем за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; строить [[Алгоритм Укконена| суффиксное дерево]], [[Алгоритм Карккайнена-Сандерса| суффиксный массив]] и преобразовывать одно в другое.&lt;br /&gt;
&lt;br /&gt;
===Поиск строки максимальной длины, ветвящейся влево и вправо===&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Строка &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; называется ветвящейся вправо в &amp;lt;tex&amp;gt;t&amp;lt;/tex&amp;gt;''' (англ. ''right branching string''), если существуют символы &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;d&amp;lt;/tex&amp;gt;, такие что &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\ne&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;d&amp;lt;/tex&amp;gt; : &amp;lt;tex&amp;gt;sc&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;sd&amp;lt;/tex&amp;gt; {{---}} подстроки &amp;lt;tex&amp;gt;t&amp;lt;/tex&amp;gt;. Аналогично, '''ветвящаяся влево''' (англ. ''left branching''), если &amp;lt;tex&amp;gt;cs&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;ds&amp;lt;/tex&amp;gt; {{---}} подстроки &amp;lt;tex&amp;gt;t&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:RightMergingSS.png|thumb|400px|center|Суффиксное дерево для строки &amp;lt;tex&amp;gt;aabcabd&amp;lt;/tex&amp;gt;]]&lt;br /&gt;
Построим cуффиксное дерево при помощи [[Алгоритм Укконена|алгоритма Укконена]]. В полученном дереве не листовой вершине &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; будет соответствовать подстрока &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, которая ветвится вправо, при условии, что количество &amp;quot;хороших&amp;quot; детей вершины &amp;lt;tex&amp;gt;v &amp;gt; 2&amp;lt;/tex&amp;gt; (&amp;quot;хорошие&amp;quot; дети {{---}} листы, метка которых &amp;lt;tex&amp;gt;\ne\$&amp;lt;/tex&amp;gt;). В примере для строки &amp;lt;tex&amp;gt;aabcabd&amp;lt;/tex&amp;gt; это &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;ab&amp;lt;/tex&amp;gt;. Далее введём термины ''левый символ'' и ''вершина различная влево'', чтобы найти строки, ветвящиеся влево.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Левый символ''' для позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt; {{---}} это символ &amp;lt;tex&amp;gt;S(i-1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
'''Левым символом''' листа &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt; называется ''левый символ'' начала суффикса, ведущего в этот лист.&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Вершина &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; различна влево''', если как минимум два листа в поддереве &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; имеют различные ''левые символы''. По определению лист не может быть различным влево.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:LeftBranchingSS.png|thumb|400px|right|Левые вершины и символы для суффиксного дерева над строкой &amp;lt;tex&amp;gt;aabcabd&amp;lt;/tex&amp;gt; (символом &amp;lt;tex&amp;gt;\#&amp;lt;/tex&amp;gt; помечены различные влево вершины)]]&lt;br /&gt;
Допустим, что строка ветвится влево. Пусть существуют подстроки &amp;lt;tex&amp;gt;cs&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;ds&amp;lt;/tex&amp;gt;, тогда есть два суффикса, начинающиеся с &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, которым соответствуют различные листовые вершины с различными левыми символами (&amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;d&amp;lt;/tex&amp;gt;). Также в дереве существует внутренняя вершина &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt;, соответствующая строке &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;. Из определения следует, что &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; различна влево.&lt;br /&gt;
&lt;br /&gt;
Чтобы найти строки, ветвящиеся влево, нужно проверить все внутренние вершины суффиксного дерева на различность влево. Если какая-то вершина &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; будет различна влево и удовлетворять свойству ветвимости право, то строка, соответствующая вершине &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; будет ветвится вправо и влево.&lt;br /&gt;
&lt;br /&gt;
Чтобы найти вершины различные влево, нужно хранить левый символ для каждой вершины или информацию о том, что она различна влево. Для поиска строки, ветвящейся влево, нужно промаркировать всё дерево. Сделать это можно при помощи [[Обход в глубину, цвета вершин| обхода в глубину]]. Начиная с корня, спускаясь вниз, для листов левый символ уже известен {{---}} при добавление нового суффикса в дерево записываем левый символ для него в лист, для вершины &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; запустим проверку.&lt;br /&gt;
&lt;br /&gt;
:'''Случай 1.''' Если среди левых детей &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; есть хотя бы один, удовлетворяющий свойству различности влево, то обозначаем &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; как различную влево вершину (в суффиксном дереве свойство различности влево передаётся от детей к родителю {{---}} строка соответствующая вершине &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; и строка соответствующая ребёнку &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; начинаются с одного и того же символа).&lt;br /&gt;
&lt;br /&gt;
:'''Случай 2.''' Если среди левых символов детей &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; нет ни одного со свойством различная влево вершина, то проверяем на совпадение левые символы детей.&lt;br /&gt;
&lt;br /&gt;
::'''Случай 2.1.''' Если все левые символ детей &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; одинаковы и эквивалентны &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;, то записываем в &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; этот символ &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
::'''Случай 2.2.''' Если не все левые символы детей &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; эквивалентны, то записываем в &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt;, что она различна влево.&lt;br /&gt;
&lt;br /&gt;
Так как время проверки &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt; пропорционально числу детей, время работы всего алгоритма {{---}} &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Теперь несложно среди всех найденных строк найти строку максимальной длины (также этот алгоритм можно использовать для нахождения количества строк, ветвящихся влево и вправо).  &lt;br /&gt;
&lt;br /&gt;
Таким образом можно найти строку максимальной длины, ветвящуюся влево и вправо, за время &amp;lt;tex&amp;gt;ST+O(n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;ST&amp;lt;/tex&amp;gt; {{---}} время построения суффиксного дерева.&lt;br /&gt;
&lt;br /&gt;
==См. также==&lt;br /&gt;
* [[Суффиксный бор|Суффиксный бор]]&lt;br /&gt;
* [[Суффиксный массив| Суффиксный массив]]&lt;br /&gt;
* [[Алгоритм Укконена| Алгоритм Укконена]] &lt;br /&gt;
&lt;br /&gt;
==Источники информации==&lt;br /&gt;
*''Дэн Гасфилд'' — '''Строки, деревья и последовательности в алгоритмах: Информатика и вычислительная биология''' — СПб.: Невский Диалект; БХВ-Петербург, 2003. — 654 с: ил.&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Словарные структуры данных ]]&lt;/div&gt;</summary>
		<author><name>37.204.71.254</name></author>	</entry>

	</feed>