Сжатое суффиксное дерево — различия между версиями
(→Построение суффиксного дерева) |
(pseudocode correction and some other small fixes) |
||
Строка 1: | Строка 1: | ||
− | [[Суффиксный бор|Суффиксный бор]] {{---}} удобная структура данных для поиска подстроки в строке, но она | + | [[Суффиксный бор|Суффиксный бор]] {{---}} удобная структура данных для поиска подстроки в строке, но она требует порядка квадрата длины исходной строки памяти. Оптимизацией суффиксного бора, требующей линейное количество памяти, является '''сжатое суффиксное дерево''' рассматриваемое далее. |
==Определение== | ==Определение== | ||
Строка 12: | Строка 12: | ||
[[Файл:Suffix_tree_3.png|thumb|right|Суффиксное дерево для строки <tex>xabxa</tex> с защитным символом]] | [[Файл:Suffix_tree_3.png|thumb|right|Суффиксное дерево для строки <tex>xabxa</tex> с защитным символом]] | ||
− | Рассмотрим дерево для строки <tex>xabxa</tex> | + | '''Данное определение порождает следующую проблему:''' <br/> |
+ | Рассмотрим дерево для строки <tex>xabxa</tex>: суффикс <tex>xa</tex> является префиксом суффикса <tex>xabxa</tex>, а, значит, этот суффикс не закачивается в листе. Для решения проблемы в конце строки <tex>s</tex> добавляют символ, не входящий в исходный алфавит: '''''защитный''''' символ. Обозначим его как <tex>\$</tex>. Любой суффикс строки с защитным символом действительно заканчивается в листе и только в листе, т. к. в такой строке не существует двух различных подстрок одинаковой длины, заканчивающихся на <tex>\$</tex>. | ||
− | Далее <tex>n</tex> - длина строки <tex>s</tex> с защитным символом. | + | Далее <tex>n</tex> {{---}} длина строки <tex>s</tex> с защитным символом. |
==Количество вершин== | ==Количество вершин== | ||
− | По определению, в суффиксном дереве содержится <tex>n</tex> листьев. | + | По определению, в суффиксном дереве содержится <tex>n</tex> листьев. Оценим количество внутренних вершин такого дерева. |
{{Лемма | {{Лемма | ||
Строка 27: | Строка 28: | ||
'''База''' | '''База''' | ||
− | При <tex>n = 2</tex> в дереве одна внутренняя вершина | + | При <tex>n = 2</tex> в дереве одна внутренняя вершина, следовательно утверждение верно. |
'''Переход''' <tex>n \rightarrow n + 1</tex> | '''Переход''' <tex>n \rightarrow n + 1</tex> | ||
− | Возьмем вершину в дереве с <tex>n + 1</tex> листами, у которой два ребенка - листья. Рассмотрим возможные случаи: | + | Возьмем вершину в дереве с <tex>n + 1</tex> листами, у которой два ребенка {{---}} листья. Рассмотрим возможные случаи: |
− | 1) У нее более двух детей. Тогда отрежем от нее лист. Получим дерево с <tex>n</tex> листьями, причем в нем количество внутренних вершин такое же, как в исходном дереве. Но у полученного дерева по индукционному предположению менее <tex>n</tex> внутренних вершин, значит, для исходного дерева лемма верна. | + | 1) У нее более двух детей. Тогда отрежем от нее лист. Получим дерево с <tex>n</tex> листьями, причем в нем количество внутренних вершин такое же, как в исходном дереве. Но у полученного дерева по индукционному предположению менее <tex>n</tex> внутренних вершин, а, значит, и для исходного дерева лемма верна. |
− | 2) У нее ровно два ребенка. Отрежем их, получим дерево с <tex>n</tex> листьями, количество внутренних вершин которого на <tex>1</tex> меньше, чем в исходном дереве. Тогда по индукционному предположению у него менее <tex>n</tex> внутренних вершин, значит, в исходном дереве их меньше <tex>n | + | 2) У нее ровно два ребенка. Отрежем их, получим дерево с <tex>n - 1</tex> листьями, количество внутренних вершин которого на <tex>1</tex> меньше, чем в исходном дереве. Тогда по индукционному предположению у него менее <tex>n - 1</tex> внутренних вершин, значит, в исходном дереве их меньше <tex>n</tex>. |
}} | }} | ||
==Занимаемая память== | ==Занимаемая память== | ||
− | Представим дерево как массив <tex> | + | Представим дерево как двумерный массив размера <tex>|V| \times |\Sigma|</tex>, где <tex>|V|</tex> {{---}} количество вершин в дереве, <tex>|\Sigma|</tex> {{---}} мощность алфавита. Для любого суффиксного дерева верна предыдущая лемма (у каждой вершины, по определению, не менее двух детей), значит, <tex>|V| = O(2 n)</tex>. Каждая <tex>[i][j]</tex> ячейка содержит информацию о том, в какую вершину ведет ребро из <tex>i</tex>-ой вершины по <tex>j</tex>-ому символу и индексы <tex>l, r</tex> начала и конца подстроки, записанной на данном переходе. Итак, дерево занимает <tex>O(n|\Sigma|)</tex> памяти. |
==Построение суффиксного дерева== | ==Построение суффиксного дерева== | ||
− | Рассмотрим наивный алгоритм построения суффиксного дерева | + | Рассмотрим наивный алгоритм построения суффиксного дерева строки <tex>s</tex>: |
− | + | go[0] = new Vertex() //корень | |
− | '''for''' | + | count = 0 //номер последней вершины, созданной в дереве (глобальная переменная) |
− | insert( | + | '''for''' i = 0 '''to''' n //для каждого символа строки |
+ | insert(i, n) //добавляем суффикс, начинающийся с него | ||
insert(l, r) | insert(l, r) | ||
− | + | cur = 0 | |
− | '''while''' ( | + | '''while''' (l < r) |
− | + | '''if''' go[cur][s[l]].v == -1 '''then''' //если мы не можем пойти из вершины по символу <tex> l </tex> | |
− | + | createVertex(cur, l, r) //создаем новую вершину | |
− | + | '''else''' | |
− | + | start = go[cur][s[l]].l | |
− | + | finish = go[cur][s[l]].r | |
− | + | hasCut = false | |
− | + | '''for''' j = start '''to''' finish //для каждого символа на ребре из текущей вершины | |
− | + | '''if''' s[l+j-start] <> s[j] '''then''' //если нашли не совпадающий символ | |
− | + | //создаем вершину на ребре | |
− | + | old = go[cur][s[l]] | |
− | + | createVertex(cur, l, j - 1) | |
− | + | go[count][s[j]].v = old | |
− | + | go[count][s[j]].r = j | |
− | + | go[count][s[j]].l = finish | |
+ | createVertex(count, l + j - start, r) | ||
+ | hasCut = true | ||
+ | '''break''' | ||
+ | '''if''' !hasCut '''then''' | ||
+ | cur = go[cur][s[l]].v //переходим по ребру | ||
+ | l = l + finish - start //двигаемся по суффиксу на длину подстроки, записанной на ребре | ||
+ | '''else''' | ||
+ | '''break''' | ||
− | createVertex( | + | createVertex(cur, l, r) |
− | + | go[++count] = new Vertex() | |
− | + | go[cur][s[l]].v = count | |
− | + | go[cur][s[l]].l = l | |
− | + | go[cur][s[l]].r = r | |
− | |||
Версия 14:47, 12 июня 2012
Суффиксный бор — удобная структура данных для поиска подстроки в строке, но она требует порядка квадрата длины исходной строки памяти. Оптимизацией суффиксного бора, требующей линейное количество памяти, является сжатое суффиксное дерево рассматриваемое далее.
Содержание
Определение
Определение: |
Суффиксное дерево (сжатое суффиксное дерево)
| для строки (где ) — дерево с листьями, обладающее следующими свойствами:
Данное определение порождает следующую проблему:
Рассмотрим дерево для строки : суффикс является префиксом суффикса , а, значит, этот суффикс не закачивается в листе. Для решения проблемы в конце строки добавляют символ, не входящий в исходный алфавит: защитный символ. Обозначим его как . Любой суффикс строки с защитным символом действительно заканчивается в листе и только в листе, т. к. в такой строке не существует двух различных подстрок одинаковой длины, заканчивающихся на .
Далее
— длина строки с защитным символом.Количество вершин
По определению, в суффиксном дереве содержится
листьев. Оценим количество внутренних вершин такого дерева.Лемма: |
Количество внутренних вершин дерева, каждая из которых имеет не менее двух детей, меньше количества листьев. |
Доказательство: |
Докажем лемму индукцией по количеству листьев .База При в дереве одна внутренняя вершина, следовательно утверждение верно.Переход Возьмем вершину в дереве с листами, у которой два ребенка — листья. Рассмотрим возможные случаи:1) У нее более двух детей. Тогда отрежем от нее лист. Получим дерево с 2) У нее ровно два ребенка. Отрежем их, получим дерево с листьями, причем в нем количество внутренних вершин такое же, как в исходном дереве. Но у полученного дерева по индукционному предположению менее внутренних вершин, а, значит, и для исходного дерева лемма верна. листьями, количество внутренних вершин которого на меньше, чем в исходном дереве. Тогда по индукционному предположению у него менее внутренних вершин, значит, в исходном дереве их меньше . |
Занимаемая память
Представим дерево как двумерный массив размера
, где — количество вершин в дереве, — мощность алфавита. Для любого суффиксного дерева верна предыдущая лемма (у каждой вершины, по определению, не менее двух детей), значит, . Каждая ячейка содержит информацию о том, в какую вершину ведет ребро из -ой вершины по -ому символу и индексы начала и конца подстроки, записанной на данном переходе. Итак, дерево занимает памяти.Построение суффиксного дерева
Рассмотрим наивный алгоритм построения суффиксного дерева строки
:go[0] = new Vertex() //корень count = 0 //номер последней вершины, созданной в дереве (глобальная переменная) for i = 0 to n //для каждого символа строки insert(i, n) //добавляем суффикс, начинающийся с него
insert(l, r)
cur = 0
while (l < r)
if go[cur][s[l]].v == -1 then //если мы не можем пойти из вершины по символу
createVertex(cur, l, r) //создаем новую вершину
else
start = go[cur][s[l]].l
finish = go[cur][s[l]].r
hasCut = false
for j = start to finish //для каждого символа на ребре из текущей вершины
if s[l+j-start] <> s[j] then //если нашли не совпадающий символ
//создаем вершину на ребре
old = go[cur][s[l]]
createVertex(cur, l, j - 1)
go[count][s[j]].v = old
go[count][s[j]].r = j
go[count][s[j]].l = finish
createVertex(count, l + j - start, r)
hasCut = true
break
if !hasCut then
cur = go[cur][s[l]].v //переходим по ребру
l = l + finish - start //двигаемся по суффиксу на длину подстроки, записанной на ребре
else
break
createVertex(cur, l, r) go[++count] = new Vertex() go[cur][s[l]].v = count go[cur][s[l]].l = l go[cur][s[l]].r = r
Этот алгоритм работает за время , однако алгоритм Укконена позволяет построить сжатое суффиксное дерево за .
Использование сжатого суффиксного дерева
Суффиксное дерево позволяет за линейное время найти:
- Количество различных подстрок данной строки
- Наибольшую общую подстроку двух строк
- Суффиксный массив и массив (longest common prefix) исходной строки
Источники
- Дэн Гасфилд — Строки, деревья и последовательности в алгоритмах: Информатика и вычислительная биология — СПб.: Невский Диалект; БХВ-Петербург, 2003. — 654 с: ил.