Алгоритм Укконена

advertisement
МИХАИЛ ДУБОВ
ОТДЕЛЕНИЕ ПРОГРАММНОЙ ИНЖЕНЕРИИ
НИУ ВШЭ, РОССИЯ, МОСКВА
ОБОБЩЕННЫЕ АННОТИРОВАННЫЕ
СУФФИКСНЫЕ ДЕРЕВЬЯ
ОСОБЕННОСТИ РЕАЛИЗАЦИИ.
АЛГОРИТМЫ И СТРУКТУРЫ ДАННЫХ
АЛГОРИТМЫ ПОСТРОЕНИЯ
НАИВНОЕ ПОСТРОЕНИЕ ОБОБЩЕННЫХ АСД.
ЛИНЕЙНЫЙ АЛГОРИТМ
НАИВНАЯ РЕАЛИЗАЦИЯ
Алгоритм NaiveConstruction(𝐶)
Вход. Коллекция строк 𝐶 = {𝑆1 … 𝑆𝑚 }.
Выход. Обобщенное АСД для 𝐶.
1. for 𝑖 ← 1 to 𝑚
2. for 𝑗 ← 1 to 𝑛𝑖 = 𝑆𝑖
3.
do 𝑘 ← совпадение 𝑆𝑖 [𝑗: ] с АСД
4.
for узел 𝑢 из 𝑆𝑖 𝑗: 𝑘
5.
do 𝑓 𝑢 ← 𝑓 𝑢 + 1
6.
for 𝑙 ← 𝑘 + 1 to 𝑛𝑖
7.
do вставить узел 𝑣
8.
𝑓(𝑣) ← 1
Время работы:
𝑶(𝒏𝟏 𝟐 + ⋯ + 𝒏𝒎 𝟐 )~𝑶(𝒎𝒏𝒎𝒂𝒙 𝟐 )
SCORE(𝑆, 𝑔𝑎𝑠𝑡) =
• score(𝑠) =
• 𝒑 𝑢 =
𝑆
𝑖=1 𝑠𝑐𝑜𝑟𝑒(𝑠
𝑆
𝑘
𝑖=1 𝑝(𝑛𝑜𝑑𝑒𝑖 )
𝑘
𝑓(𝑢)
𝑓(𝑝𝑎𝑟𝑒𝑛𝑡 𝑢 )
𝑖: )
(k – число
совпавших
узлов)
ДЕРЕВО КЛЮЧЕЙ ≠ СУФФИКСНОЕ ДЕРЕВО
В суффиксном дереве:
• Путь “корень → … → лист” = суффикс
• На ребрах – непустые подстроки;
• Каждый внутренний узел (кроме, может
быть, корня) имеет не менее двух
дочерних.
• 𝑶 𝒏𝟐 узлов и ребер
• Не менее 𝟐𝒏 узлов и 𝟐𝒏 − 𝟏 ребер
• Но: каждое ребро содержит
подстроку ⇒ все еще 𝑶 𝒏𝟐 памяти
СУФФИКСНОЕ ДЕРЕВО
Алгоритм LinearConstruction(𝐶)
Вход. Коллекция строк 𝐶 = {𝑆1 … 𝑆𝑚 }.
Выход. Обобщенное АСД для 𝐶.
?
SCORE(𝑆, 𝑔𝑎𝑠𝑡) =
• Оптимизация ⇒ 𝑶 𝒏 памяти
• Вычисление score – возможно!
• Возможно ли построение за 𝑶 𝒏 ?
• score(𝑠) =
• 𝒑 𝑢 =
𝑆
𝑖=1 𝑠𝑐𝑜𝑟𝑒(𝑠
𝑖: )
𝑆
𝒌
𝒊=𝟏 𝒑(𝒏𝒐𝒅𝒆𝒊 )
𝑓(𝑢)
𝑓(𝑝𝑎𝑟𝑒𝑛𝑡 𝑢 )
+𝒍−𝒌
𝒍
(l – число
совпавших
символов,
k – число узлов
в совпадении)
ПОСТРОЕНИЕ СУФФИКСНЫХ
ДЕРЕВЬЕВ ЗА ЛИНЕЙНОЕ ВРЕМЯ
Петер Вайнер, 1973
“…«Алгоритм
года 1973»!”
Эдвард МакКрейг, 1976
Эско Укконен, 1995
Алгоритм Укконена:
1. Проще для восприятия;
2. Эффективнее;
3. Первый «он-лайн»алгоритм.
АЛГОРИТМ УККОНЕНА
Алгоритм Ukkonen(𝑆)
Вход. Строка 𝑆; 𝑆 = 𝑛
Выход. Суффиксное дерево для 𝑆.
1. Построить суффиксное дерево 𝑇1 – состоящее из одной дуги,
помеченной символом 𝑆[1].
2. for 𝑖 ← 1 to 𝑛 − 1
{Фаза i+1}
3.
for 𝑗 ← 1 to 𝑖 + 1
{Продолжение j}
4.
do найти конец пути из корня с меткой 𝑆[𝑗: 𝑖].
5.
Если нужно, добавить символ 𝑆[𝑖 + 1], обеспечив присутствие в
дереве строки 𝑆[𝑗: 𝑖 + 1].
«Наивная» реализация: время работы - 𝑶(𝒏𝟑 )
• В фазе 𝑖 + 1 дерево 𝑇𝑖+1 для 𝑆[: 𝑖 + 1] строится из дерева 𝑇𝑖 для 𝑆 : 𝑖 ;
• Каждая фаза 𝑖 + 1 делится на 𝑖 + 1 продолжений; в продолжении 𝑗
фазы 𝑖 + 1 суффикс 𝑆[𝑗: 𝑖] дополняется символом 𝑆[𝑖 + 1].
АЛГОРИТМ УККОНЕНА
• Как понизить вычислительную сложность?
«Разделяй и властвуй»
𝑂(𝑛3 ) → 𝑂(𝑛2.81 )
Динамическое
программирование
𝑂(𝑛!) → 𝑂(𝑛2 2𝑛 )
Жадные алгоритмы
𝑂( 𝑉 𝐸 ) → 𝑂( 𝑉 log 𝑉 + 𝐸 )
АЛГОРИТМ УККОНЕНА
• Как понизить вычислительную сложность?
 «Программистские» приемы реализации!


По отдельности выглядят как полезная эвристика для ускорения;
Вместе дают «прорыв» во времени выполнения алгоритма.
𝑂(𝑛3 ) → 𝑂(𝑛)
ПРИЕМЫ РЕЛИЗАЦИИ АЛГОРИТМА
УККОНЕНА ЗА ЛИНЕЙНОЕ ВРЕМЯ
1. Суффиксные связи
2. Ускоренное
прохождение ребер
𝑂(𝑛2 )
𝑂(𝑛)
3. Пропуск части продолжений,
где не нужно ничего вычислять
КАК НАСЧЕТ
ОБОБЩЕННЫХ ДЕРЕВЬЕВ?
• Сведение к алгоритму Укконена для одной строки:
Алгоритм GeneralizedSuffixTree_Ukkonen(𝐶)
Вход. Коллекция строк 𝐶 = {𝑆1 … 𝑆𝑚 }.
Выход. Обобщенное суффиксное дерево для 𝐶.
1. Построить суффиксное дерево 𝑇 для 𝑆1 алгоритмом Укконена.
2. for 𝑖 ← 2 to 𝑚
3.
do найти совпадение 𝑆𝑖 с T; пусть 𝑘 ← число совпавших символов.
4.
(в этот момент все суффиксы 𝑆𝑖 [1: 𝑘] уже закодированы в 𝑇;
5.
𝑘 фаз алгоритма Укконена, таким образом, выполнены неявно)
6.
выполнить алгоритм Укконена для 𝑆𝑖 , начиная в фазе 𝑘 + 1.
КАК НАСЧЕТ
АННОТИРОВАННЫХ ДЕРЕВЬЕВ?
• Сведение к алгоритму Укконена для обобщенных
суффиксных деревьев:
Алгоритм LinearConstruction(𝐶)
Вход. Коллекция строк 𝐶 = {𝑆1 … 𝑆𝑚 }.
Выход. Обобщенное АСД для 𝐶.
1. Сформировать 𝐶 ′ = {𝑆1 $1 … 𝑆𝑚 $𝑚 }, где $𝑖 - уникальные символы.
2. Построить обобщенное суффиксное дерево 𝑇 для коллекции 𝐶 ′ используя
алгоритм с линейной сложностью, например, алгоритм Укконена.
3. for 𝑙 in 𝑙𝑒𝑎𝑣𝑒𝑠(𝑇)
4.
do 𝑓 𝑙 ← 1
5. Выполнить обход дерева T сверху вниз; в каждом внутреннем узле 𝑣
присвоить 𝑓 𝑣 ← 𝑢∈𝑇: 𝑝𝑎𝑟𝑒𝑛𝑡 𝑢 =𝑣 𝑓(𝑢).
Алгоритм основан на важном свойстве АСД: 𝑓 𝑣 =
𝑢∈𝑇: 𝑝𝑎𝑟𝑒𝑛𝑡 𝑢 =𝑣 𝑓(𝑢)
Время работы: 𝑶(𝒏𝟏 + ⋯ + 𝒏𝒎 )~𝑶(𝒎𝒏𝒎𝒂𝒙 )
НАИВНЫЙ VS. ЛИНЕЙНЫЙ АЛГОРИТМ
ЭКСПЕРИМЕНТ: СЛУЧАЙНЫЕ СТРОКИ
НАИВНЫЙ VS. ЛИНЕЙНЫЙ АЛГОРИТМ
ЭКСПЕРИМЕНТ: «НАИХУДШИЕ» СТРОКИ
ВОПРОС ПАМЯТИ
ДАЛЬНЕЙШИЕ ПУТИ ОПТИМИЗАЦИИ
ИСПОЛЬЗОВАНИЯ ПАМЯТИ
ХРАНЕНИЕ ДОЧЕРНИХ УЗЛОВ
ВЕРШИНЫ
• Хранение указателей на дочерние узлы:
1.
Хэш-таблица
+ Операции вставки и поиска узла – за 𝑶 𝟏 ;
- Требуется дополнительная память на
организацию структуры данных («идеальное
хэширование» ⇒ минимум log 2 𝑒 ∙ 𝑛 ≈ 1.44𝑛 бит
памяти на 𝑛 элементов).
2.
Отсортированный (по символам) массив
+
-
3.
4.
Чрезвычайно эффективно по памяти;
Но: мало «детей» ⇒ много пустых ячеек;
Поиск узла деградирует до 𝑶(𝐥𝐨𝐠 𝜮 );
Построение деградирует с 𝑂(𝑛) до 𝑶 𝒏 𝒍𝒐𝒈 𝜮 .
Связный список
Сбалансированные бинарные деревья
ch = {‘A’: <p1>,
‘B’: <p2>,
‘C’: <p3>,
‘X’: <p4>}
ch[0] = <p1>
ch[1] = <p2>
ch[2] = <p3>
ch[3] = <p4>
//‘A’
//‘B’
//‘C’
//‘X’
ХРАНЕНИЕ ДОЧЕРНИХ УЗЛОВ
ВЕРШИНЫ
• Возможны комбинированные подходы:
На верхних уровнях
дерева плотность
детей больше; имеет
смысл использовать
массивы.
Ближе к листьям детей
у вершин становится
меньше ⇒ используем
хэш-таблицы или
сбалансированные
бинарные деревья.
СУФФИКСНЫЕ МАССИВЫ
• Для строки длины 𝑛:
 Суффиксное дерево занимает ~ 𝟐𝟎 … 𝟒𝟎 ∙ 𝒏 байт памяти;
 Суффиксный массив занимает ~ 𝟒 … 𝟖 ∙ 𝒏 байт памяти.
СУФФИКСНЫЕ МАССИВЫ
• Откуда берутся преимущества, присущие
суффиксным массивам?
 Мы жертвуем:
 концептуальной простотой структуры данных;
 простыми алгоритмами построения и поиска подстроки (обе
операции все еще осуществимы за линейное время, но
соответствующие алгоритмы – сложнее);
 хранением связей между родительскими и дочерними узлами.
 Получаем:
 сильную экономию по памяти;
 компактно хранящуюся в памяти структуру данных ⇒ становится
возможным пейджинг.
Но можно ли в суффиксном массиве вычислять SCORE
без наличия родительско-дочерних связей?..
ДАЛЬНЕЙШАЯ РАБОТА
• Экспериментирование с организацией
суффиксных деревьев в памяти;
 возможно, с привлечением библиотеки NumPy.
• Разработка комбинированного алгоритма
построения АСД, который:
 анализирует входные тексты;
 определяет, какой из алгоритмов (Наивный/Укконен)
лучше подходит для данного случая.
• Тестирование нашей реализации на реальных
данных;
• Реализация ПС-таблицы.
ЛИТЕРАТУРА
• Gusfield, Dan, Algorithms on
Strings, Trees and Sequences:
Computer Science and
Computational Biology,
Cambridge University Press
Q&A
Download