<?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=5.18.84.13&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=5.18.84.13&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/5.18.84.13"/>
		<updated>2026-06-11T14:08:36Z</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=36517</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=36517"/>
				<updated>2014-05-01T11:28:26Z</updated>
		
		<summary type="html">&lt;p&gt;5.18.84.13: /* Построение из суффиксного массива */ небольшие правки&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Суффиксный бор|Суффиксный бор]] {{---}} удобная структура данных для поиска подстроки в строке, но она требует порядка квадрата длины исходной строки памяти. Оптимизацией суффиксного бора, требующей линейное количество памяти, является '''сжатое суффиксное дерево''' рассматриваемое далее.&lt;br /&gt;
&lt;br /&gt;
==Определение==&lt;br /&gt;
{{Определение&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;
&lt;br /&gt;
[[Файл:Suffix_tree_3.png|thumb|right|Суффиксное дерево для строки &amp;lt;tex&amp;gt;xabxa&amp;lt;/tex&amp;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;
&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;
Докажем лемму индукцией по количеству листьев &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;
1) У нее более двух детей. Тогда отрежем от нее лист. Получим дерево с &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;
&lt;br /&gt;
2) У нее ровно два ребенка. Отрежем их, получим дерево с &amp;lt;tex&amp;gt;n - 1&amp;lt;/tex&amp;gt; листьями, количество внутренних вершин которого на &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; меньше, чем в исходном дереве. Тогда по индукционному предположению у него менее &amp;lt;tex&amp;gt;n - 1&amp;lt;/tex&amp;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;|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;[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;
 go[0] = new Vertex() //корень&lt;br /&gt;
 count = 0 //номер последней вершины, созданной в дереве (глобальная переменная)&lt;br /&gt;
 '''for''' i = 0 '''to''' n //для каждого символа строки&lt;br /&gt;
     insert(i, n) //добавляем суффикс, начинающийся с него&lt;br /&gt;
&lt;br /&gt;
 insert(l, r)&lt;br /&gt;
     cur = 0 &lt;br /&gt;
     '''while''' (l &amp;lt; r)&lt;br /&gt;
         '''if''' go[cur][s[l]].v == -1  '''then''' //если мы не можем пойти из вершины по символу &amp;lt;tex&amp;gt; l &amp;lt;/tex&amp;gt;&lt;br /&gt;
             createVertex(cur, l, r) //создаем новую вершину &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 //для каждого символа на ребре из текущей вершины&lt;br /&gt;
                 '''if''' s[l+j-start] &amp;lt;&amp;gt; s[j] '''then''' //если нашли не совпадающий символ&lt;br /&gt;
                     //создаем вершину на ребре&lt;br /&gt;
                     old = go[cur][s[l]]&lt;br /&gt;
                     createVertex(cur, l, j - 1)&lt;br /&gt;
                     go[count][s[j]].v = old&lt;br /&gt;
                     go[count][s[j]].r = j&lt;br /&gt;
                     go[count][s[j]].l = finish&lt;br /&gt;
                     createVertex(count, l + j - start, r)&lt;br /&gt;
                     hasCut = true&lt;br /&gt;
                     '''break'''&lt;br /&gt;
             '''if''' !hasCut '''then'''&lt;br /&gt;
                 cur = go[cur][s[l]].v //переходим по ребру&lt;br /&gt;
                 l = l + finish - start //двигаемся по суффиксу на длину подстроки, записанной на ребре&lt;br /&gt;
             '''else'''&lt;br /&gt;
                 '''break'''&lt;br /&gt;
&lt;br /&gt;
 createVertex(cur, l, 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;
&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;Node&amp;lt;/tex&amp;gt; мы будем хранить предка &amp;lt;tex&amp;gt;parent&amp;lt;/tex&amp;gt;, [[Стек| стек]] детей в лексикографическом порядке ребер &amp;lt;tex&amp;gt;children&amp;lt;/tex&amp;gt;, глубину вершины в символах от корня &amp;lt;tex&amp;gt;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;tex&amp;gt;previous&amp;lt;/tex&amp;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; общих символов, что позволит ускорить добавление.&lt;br /&gt;
&lt;br /&gt;
Алгоритм добавления суффикса:&lt;br /&gt;
# Если мы находимся в корне, либо &amp;lt;tex&amp;gt;depth = lcp&amp;lt;/tex&amp;gt;, новый суффикс нужно добавить к детям.&lt;br /&gt;
# Если &amp;lt;tex&amp;gt;parent.depth &amp;lt; lcp&amp;lt;/tex&amp;gt;, новый суффикс будет идти из середины ребра к предку. Вставим между нами и предком вершину с глубиной &amp;lt;tex&amp;gt;lcp&amp;lt;/tex&amp;gt;.&lt;br /&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;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;
 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;
Таким образом, мы умеем за &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;
&lt;br /&gt;
==Источники==&lt;br /&gt;
*''Дэн Гасфилд'' — '''Строки, деревья и последовательности в алгоритмах: Информатика и вычислительная биология''' — СПб.: Невский Диалект; БХВ-Петербург, 2003. — 654 с: ил.&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;/div&gt;</summary>
		<author><name>5.18.84.13</name></author>	</entry>

	</feed>