Алгоритм Укконена — различия между версиями
KK (обсуждение | вклад) м (→Использование суффиксных ссылок) |
KK (обсуждение | вклад) м (→Использование суффиксных ссылок) |
||
Строка 59: | Строка 59: | ||
Рассмотрим один такой переход, пусть мы только что продлили суффикс <tex>s[j..i-1]</tex> до суффикса <tex>s[j..i]</tex>. Теперь с помощью построенных ссылок найдем конец суффикса <tex>s[j+1..i-1]</tex>, чтобы продлить его до суффикса <tex>s[j+1..i]</tex>. Пройдем вверх по дереву до ближайшей внутренней вершины <tex>v</tex>, в которую ведет путь, помеченный <tex>s[j..r]</tex>. У вершины <tex>v</tex> есть суффиксная ссылка, так как ссылка для внутренней вершины строится внутри фазы создания этой вершины. Пусть суффиксная ссылка ведет в вершину <tex>u</tex>, которой соответствует путь с пометкой <tex>s[j+1..r]</tex>. Теперь пройдем от вершины <tex>u</tex> вниз по дереву к концу суффикса <tex>s[j+1..i-1]</tex>, и сделаем продление до суффикса <tex>s[j+1..i]</tex>. | Рассмотрим один такой переход, пусть мы только что продлили суффикс <tex>s[j..i-1]</tex> до суффикса <tex>s[j..i]</tex>. Теперь с помощью построенных ссылок найдем конец суффикса <tex>s[j+1..i-1]</tex>, чтобы продлить его до суффикса <tex>s[j+1..i]</tex>. Пройдем вверх по дереву до ближайшей внутренней вершины <tex>v</tex>, в которую ведет путь, помеченный <tex>s[j..r]</tex>. У вершины <tex>v</tex> есть суффиксная ссылка, так как ссылка для внутренней вершины строится внутри фазы создания этой вершины. Пусть суффиксная ссылка ведет в вершину <tex>u</tex>, которой соответствует путь с пометкой <tex>s[j+1..r]</tex>. Теперь пройдем от вершины <tex>u</tex> вниз по дереву к концу суффикса <tex>s[j+1..i-1]</tex>, и сделаем продление до суффикса <tex>s[j+1..i]</tex>. | ||
− | Мы знаем сколько символов просмотрели на ребре ведущем вершину, из которой мы поднялись перед тем как сделать переход по суффиксной ссылке. Поэтому не сложно осознать, что при спуске к концу <tex>s[j+1..i-1]</tex> суффикса не нужно просматривать все символы, а можно сразу перейти к проверке существования перехода по <tex>s_i</tex> | + | Мы знаем сколько символов просмотрели на ребре ведущем вершину, из которой мы поднялись перед тем как сделать переход по суффиксной ссылке. Поэтому не сложно осознать, что при спуске к концу <tex>s[j+1..i-1]</tex> суффикса не нужно просматривать все символы, а можно сразу перейти к проверке существования перехода по символу <tex>s_i</tex>. |
=== Построение суффиксных ссылок === | === Построение суффиксных ссылок === |
Версия 11:43, 1 мая 2015
Алгоритм Укконена (англ. Ukkonen's algorithm) — алгоритм построения суффиксного дерева для заданной строки за линейное время.
Содержание
Алгоритм за O(n3)
Рассмотрим сначала наивный метод, который строит дерево за время
, где — длина исходной строки . В дальнейшем данный алгоритм будет оптимизирован таким образом, что будет достигнута линейная скорость работы.Определение: |
Неявное суффиксное дерево (англ. implicit suffix tree, IST) строки | — это суффиксное дерево, построенное для строки без добавления .
Алгоритм последовательно строит неявные суффиксные деревья для всех префиксов исходного текста
Алгоритм состоит из
итераций так как в исходном тексте суффиксов. На каждой фазе происходит продление всех суффиксов по порядку, что требует времени. Следовательно, общая асимптотика алгоритма .Псевдокод алгоритма за O(n3)
for i = 1 .. n for j = 1 .. i treeExtend(s[j..i]) // добавление текущего суффикса работает за линейное время
Замечание: на первый взгляд, более логичным подходом кажется добавление всех суффиксов строки в дерево по очереди, получив сразу алгоритм со временем работы алгоритма МакКрейта.
. Однако осуществить улучшение данного алгоритма до линейного времени работы будет намного сложней, хотя именно в этом и заключается сутьПродление суффиксов
Ниже приведены возможные случаи, которые могут возникнуть при добавлении символа
ко всем суффиксам префикса .Суффиксные ссылки
Определение: |
Пусть | обозначает произвольную строку, где — ее первый символ, а — оставшаяся подстрока (возможно пустая). Если для внутренней вершины с путевой меткой существует другая вершина с путевой меткой , то ссылка из в называется суффиксной ссылкой (англ. suffix link).
Лемма (Существование суффиксных ссылок): |
Для любой внутренней вершины суффиксного дерева существует суффиксная ссылка, ведущая в некоторую внутреннюю вершину . |
Доказательство: |
Рассмотрим внутренную вершину | с путевой меткой . Так как эта вершина внутренняя, ее путевая метка ветвится справа в исходной строке. Тогда очевидно подстрока тоже ветвится справа в исходной строке, и ей соответствует некоторая внутренняя вершина . По определению суффиксная ссылка вершины ведет в
Использование суффиксных ссылок
Сейчас на каждой итерации алгоритм выполняет
действий, так как до конца каждого суффикса мы спускаемся из корня. Заметим, что быстрее было бы спуститься от корня только один раз, до конца суффикса, который мы продлеваем первым на текущем шаге, а потом каким-то способом переходить от конца одного суффикса к концу другого за . Суффиксные ссылки используются как раз для того, чтобы делать подобные переходы.Рассмотрим один такой переход, пусть мы только что продлили суффикс
до суффикса . Теперь с помощью построенных ссылок найдем конец суффикса , чтобы продлить его до суффикса . Пройдем вверх по дереву до ближайшей внутренней вершины , в которую ведет путь, помеченный . У вершины есть суффиксная ссылка, так как ссылка для внутренней вершины строится внутри фазы создания этой вершины. Пусть суффиксная ссылка ведет в вершину , которой соответствует путь с пометкой . Теперь пройдем от вершины вниз по дереву к концу суффикса , и сделаем продление до суффикса .Мы знаем сколько символов просмотрели на ребре ведущем вершину, из которой мы поднялись перед тем как сделать переход по суффиксной ссылке. Поэтому не сложно осознать, что при спуске к концу
суффикса не нужно просматривать все символы, а можно сразу перейти к проверке существования перехода по символу .Построение суффиксных ссылок
Заметим, что в процессе построения суффиксного дерева уже построенные суффиксные ссылки никак не изменяются. Опишем процесс построения суффиксной ссылки для новой созданной внутренней вершины. Пусть в результате продления суффикса
была создана новая внутренняя вершина . Не будем специально искать, куда должна указывать ссылка. Перейдем к следующему шагу текущей фазы, на котором суффикс будет увеличен до суффикса . Этот суффикс может так же оканчиваться на ребре или в уже созданной раннее внутренней вершине, в листе он, очевидно, заканчиваться не может. Тогда в первом случае будет создана новая внутренняя вершина , а во втором эта вершина уже будет существовать. Проведем суффиксную ссылку из вершины в вершину .Оценка числа переходов
Определение: |
Глубиной вершины | назовем число ребер на пути от корня до вершины
Лемма: |
. |
Доказательство: |
Заметим, что на пути | в дереве по суффиксу не более чем на одну вершину меньше, чем на пути по суффиксу . Каждой вершине на пути соответствует вершина на пути , в которую ведет суффиксная ссылка. Разница в одну вершину возникает, если первому ребру в пути соответсвует метка из одного символа , тогда суффиксная ссылка из вершины, в которую ведет это ребро, будет вести в корень.
Лемма: |
Число переходов по ребрам внутри фазы номер не превышает |
Доказательство: |
Оценим количество переходов по ребрам при поиске конца суффикса. Переход до ближайшей внутренней вершины уменьшает высоту на | . Переход по суффиксной ссылке уменьшает высоту не более чем на (по лемме, доказанной выше). Значит в течение одной фазы вверх мы переходим не более раз. Но внутри одной фазы начальная глубина не меньше конечной (так как длины суффиксов убывают до ), поэтому вниз мы могли пройти не более ребер. Итого получаем оценку .
Асимтотика алгоритма с использованием суффиксных ссылок
Благодаря суффиксным ссылкам мы только один раз спускаемся от корня, а дольше делаем переходы за
между концами суффиксов, а таких переходов, по доказанной выше лемме, не более на текущей итерации. Следовательно, количество действий на одной итерации снижается с до . Таким образом общая асимптотика алгоритма улучшилась до .Линейный алгоритм
Чтобы улучшить время работы данного алгоритма до
, нужно использовать линейное количество памяти, поэтому метка каждого ребра будет храниться как два числа — позиции ее самого левого и самого правого символов в исходном тексте.Лемма (Стал листом — листом и останешься): |
Если в какой-то момент работы алгоритма Укконена будет создан лист с меткой (для суффикса, начинающегося в позиции строки ), он останется листом во всех последовательных деревьях, созданных алгоритмом.
|
Доказательство: |
Это верно потому, что у алгоритма нет механизма продолжения листового ребра дальше текущего листа. Если есть лист с суффиксом | , правило продолжения 1 будет применяться для продолжения на всех последующих фазах.
Лемма (Правило 3 заканчивает дело): |
В любой фазе, если правило продления 3 применяется в продолжении , оно будет реализовываться во всех дальнейших продолжениях (от по ) до конца фазы.
|
Доказательство: |
При использовании правила продолжения 3 путь, помеченный | в текущем дереве, должен продолжаться символом , и точно так же продолжается путь, помеченный , поэтому правило 3 применяется в продолжениях
Когда используется 3-е правило продления суффикса, никакой работы делать не нужно, так как требуемый суффикс уже в дереве есть. Поэтому можно заканчивать текущую итерацию после первого же использования этого правила.
Так как лист навсегда останется листом, зададим метку ребра ведущего в этот лист как
, где — ссылка на переменную, хранящую конец текущей подстроки. На следующих итерациях к этому ребру может применяться правило ответвления, но при этом будет меняться только левый(начальный) индекс . Таким образом мы сможем удлинять все суффиксы, заканчивающиеся в листах за .Будем называть самым длинным не уникальным суффиксом суффикс, который заканчиватся на ребре и имеет максимальную длину из всех таких суффиксов. Заметим, что так как суффиксы, продленные по второму правилу, заканчиваются в листах и далее будут продляться за
, то на очередной итерации мы можем начинать продлять суффиксы не с , а с самого длинного не уникального суффикса , т.е. суффикса на котором мы остановились на прошлой итерации, применив пустое правило продления. Следовательно, храня ссылку на место остановки на предыдущей итерации, мы можем не спускаться от корня к концу суффикса, а сразу продлевать его символом .Итоговая оценка времени работы
В течение работы алгоритма создается не более лемме внутренних вершин в дереве меньше чем листьев, следовательно, всего вершин в получившемся дереве будет . Все суффиксы, которые заканчиваются в листах, благодаря первой лемме на каждой итерации мы увеличиваем на текущий символ по умолчанию за . Текущая фаза алгоритма идет пока мы явно не продлим все суффиксы или, по второй лемме, пока не будет использовано правило продления 3. При явном продлении суффикса всегда создается новый лист, в котором он заканчивается, не сложно заметить, что этот суффикс на всех последующих итерация будет продлеваться по правилу 1(за ), тогда на всех итерациях суммарно не может быть сделано более явных и неявных продлений, так же теперь мы не спускаемся от корня к концу первого суффикса на текущей итерации за , а сразу, за , начинаем его продление, тогда одна итерация в среднем будет выполняться за . Таким образом при использовании всех приведенных эвристик, алгоритм Укконена работает за .
листов, так как в исходном тексте суффиксов. ПоМинусы алгоритма Укконена
Не смотря на то, что данный алгоритм является одним из самых простых в понимании алгоритмов для построения суффиксных деревьев и использует online подход, у него есть серьезные недостатки, из-за которых его нечасто используют на практике:
- Размер суффиксного дерева сильно превосходит входные данные, поэтому при очень больших входных данных алгоритм Укконена сталкивается с проблемой memory bottleneck problem(другое ее название thrashing)[1].
- Для несложных задач, таких как поиск подстроки, проще и эффективней использовать, например, префикс-функцию.
- Существенно использует размер алфавита. Чтобы отвечать на запрос о существований перехода по текущему символу за нужно хранить линейное количество информации от размера алфавита. Поэтому, если алфавит очень большой требуется чрезмерный объем памяти. Если же экономить на памяти или если изначально алфавит неизвестен, то время на запрос ухудшится до .
- Константное время на одну итерацию — это амортизированная оценка, в худшем случае одна фаза может выполняться за [2], хоть и строит дерево за , но на одну итерацию в худшем случае тратит времени. времени. Например, алгоритм Дэни Бреслауера и Джузеппе Итальяно
- На сегодняшний день существуют кэш-эффективные алгоритмы, которые превосходят алгоритм Укконена на современных процессорах[3].
- Так же алгоритм предполагает, что дерево полностью должно быть загружено в оперативную память, а при больших размерах входных данных это может быть затруднительно, поэтому хотелось бы, чтобы дерево было загружено "частично"[4].
См. также
Примечания
- ↑ Marina Barsky — Suffix trees for very large inputs.
- ↑ Dany Breslauer, Giuseppe F. Italiano — Near Real-Time Suffix Tree Construction via the Fringe Marked Ancestor Problem.
- ↑ Yuanyuan Tian, Sandeep Tata, Richard A. Hankins, Jignesh M. Patel — Practical methods for constructing suffix trees.
- ↑ Woong-Kee Loh, Yang-Sae Moon, Wookey Lee — A fast divide-and-conquer algorithm for indexing human genome sequences.
Источники информации
- Дэн Гасфилд — Строки, деревья и последовательности в алгоритмах: Информатика и вычислительная биология — СПб.: Невский Диалект; БХВ-Петербург, 2003. — 654 с: ил.
- Юрий Лифшиц — Построение суффиксного дерева за линейное время.
- MAXimal :: algo :: Суффиксное дерево. Алгоритм Укконена
- Habrahabr — Построение суффиксного дерева: алгоритм Укконена