Сжатое суффиксное дерево — различия между версиями
(→Занимаемая память) |
(→Построение суффиксного дерева) |
||
| Строка 42: | Строка 42: | ||
'''for''' <tex> i \leftarrow 0 </tex> '''to''' <tex> n </tex> '''do''' //для каждого символа строки | '''for''' <tex> i \leftarrow 0 </tex> '''to''' <tex> n </tex> '''do''' //для каждого символа строки | ||
insert(<tex>i, n</tex>) //добавляем суффикс, начинающийся с него | insert(<tex>i, n</tex>) //добавляем суффикс, начинающийся с него | ||
| + | |||
| + | insert(l,r) //процедура вставки | ||
| + | <tex> cur \leftarrow root </tex> //инициализируем текущую вершину корнем | ||
| + | '''while''' (<tex> i < r </tex>) | ||
| + | '''if''' <tex> go[cur][s[i]].v = 0 </tex> //если мы не можем пойти из вершины по символу <tex> i </tex> | ||
| + | create_vertex(<tex>cur, new V, i, r, s[i]</tex>) //создаем новую вершину | ||
| + | '''else''' | ||
| + | <tex>start \leftarrow go[cur][s[i]].l </tex> | ||
| + | <tex>finish \leftarrow go[cur][s[i]].r </tex> | ||
| + | '''for''' <tex> j = start </tex> '''to''' <tex> finish </tex> //для каждого символа на ребре из текущей вершины | ||
| + | '''if''' <tex>s[i+j-start] <>s[j] </tex> //нашли не совпадающий символ | ||
| + | '''разбить ребро''' | ||
| + | '''break''' | ||
| + | '''if''' '''ребро не разбивали''' | ||
| + | <tex>cur \leftarrow go[cur][s[i]].v </tex> //переходим по ребру | ||
| + | <tex>i \leftarrow i + finish - start </tex> //двигаемся по суффиксу на длину подстроки, записанной на ребре | ||
| + | |||
Этот алгоритм работает за время<tex>O(n^2)</tex>, однако существует [[Алгоритм Укконена| алгоритм Укконена]], позволяющий построить дерево за время <tex>O(n)</tex>. | Этот алгоритм работает за время<tex>O(n^2)</tex>, однако существует [[Алгоритм Укконена| алгоритм Укконена]], позволяющий построить дерево за время <tex>O(n)</tex>. | ||
Версия 22:31, 23 апреля 2012
Суффиксный бор — удобная структура для поиска подстроки в строке, но занимающая много места в памяти. Рассмотрим все такие пути от до в суффиксном боре, в которых каждая вершина имеет только одного сына. Такие пути можно сжать до одного ребра , пометив его всеми встречающимися на пути символами. Получившееся дерево носит название сжатое суффиксное дерево.
Содержание
Определение
Суффиксное дерево (сжатое суффиксное дерево) для строки (где ) — ориентированное дерево, с ровно листами, каждая внутренняя вершина которого, отличная от корня, имеет не меньше двух детей, а каждое ребро помечено непустой подстрокой строки и символом, с которого начинается эта подстрока. Никакие два ребра, выходящие из одной и той же вершины, не могут иметь одинаковых символьных пометок. Суффиксное дерево содержит все суффиксы строки : для каждого листа конкатенация подстрок на ребрах пути от корня к листу в точности составляет суффикс, который начинается в позиции , то есть .
Существование сжатого суффиксного дерева
Определение суффиксного дерева не гарантирует, что такое дерево существует для любой строки . Если один суффикс совпадает с префиксом другого суффикса, то построить суффиксное дерево, удовлетворяющее данному выше определению, невозможно, поскольку путь для первого суффикса не сможет закончиться в листе. Например, для строки суффикс является префиксом суффикса Во избежание этого в конце строки добавляется символ, не входящий в исходный алфавит. Такой символ называется защитным. Как правило, защитный символ обозначается . Любой суффикс строки с защитным символом заканчивается в листе, т.к. этот символ не встречается в строке нигде, кроме позиции последнего символа.
Далее - длина строки с защитным символом.
Хранение суффиксного дерева
Как уже было отмечено выше, каждое ребро дерева помечается подстрокой исходной строки . Можно для каждого ребра хранить не саму подстроку, а индексы начала и конца подстроки в исходной строке — . Итак, с каждым ребром дерева ассоциируются две инцидентные ей вершины, символ, с которого начинается подстрока на ребре и два числа . Представим дерево как массив , где — количество вершин в дереве. Каждая ячейка массива содержит информацию о том, в какую вершину ведет ое ребро по ому символу и индексы подстроки на ребре.
Количество вершин
В сжатом суффиксном дереве содержится листьев, т.к. каждый суффикс строки заканчивается в листе. Рассмотрим теперь количество внутренних вершин такого дерева.
| Лемма: |
Количество внутренних вершин дерева, каждая из которых имеет не менее двух детей, меньше количества листьев. |
| Доказательство: |
|
Докажем лемму индукцией по количеству листьев . База При в дереве одна внутренняя вершина - верно. Переход Рассмотрим все вершины в дереве для строки длины , у которых хотя бы один из детей - лист. Если среди них есть вершина, у которой более двух детей, отрежем от нее лист. Получим дерево с листьями, удовлетворяющее условию леммы по индукционному предположению, причем в нем количество внутренних вершин равно количеству внутренних вершин в исходном дереве. Тогда у полученного дерева менее внутренних вершин, значит в исходном дереве количество внутренних вершин так же меньше количества листьев. Иначе среди этих вершин есть вершина, у которой оба ребенка - листья. Отрежем оба этих листа, получим дерево с листьями, удовлетворяющее условию леммы, количество внутренних вершин которого на меньше количества внутренних вершин в исходном дереве. Тогда, по индукционному предположению, у полученного дерева менее внутренних вершин, значит в исходном дереве количество внутренних вершин меньше . |
Занимаемая память
Очевидно, суффиксное дерево в виде массива занимает памяти. Так как любое суффиксное дерево удовлетворяет условиям леммы, и все его внутренние вершины, по определению, имеют не менее двух детей, то количество внутренних вершин в нем меньше количества листьев, равного , поэтому для его хранения требуется памяти.
Построение суффиксного дерева
Рассмотрим наивный алгоритм построения суффиксного дерева:
for to do //для каждого символа строки insert() //добавляем суффикс, начинающийся с него
insert(l,r) //процедура вставки
//инициализируем текущую вершину корнем
while ()
if //если мы не можем пойти из вершины по символу
create_vertex() //создаем новую вершину
else
for to //для каждого символа на ребре из текущей вершины
if //нашли не совпадающий символ
разбить ребро
break
if ребро не разбивали
//переходим по ребру
//двигаемся по суффиксу на длину подстроки, записанной на ребре
Этот алгоритм работает за время, однако существует алгоритм Укконена, позволяющий построить дерево за время .
Использование
Суффиксное дерево позволяет за линейное время найти:
- Количество различных подстрок данной строки
- Наибольшую общую подстроку двух строк
- Суффиксный массив и массив (longest common prefix) исходной строки
Источники
Дэн Гасфилд — Строки, деревья и последовательности в алгоритмах: Информатика и вычислительная биология — СПб.: Невский Диалект; БХВ-Петербург, 2003. — 654 с: ил.