Алгоритм МакКрейта — различия между версиями
Genyaz (обсуждение | вклад) (Новая страница: «{{В разработке}} '''Алгоритм МакКрейта''' — алгоритм построения [[Сжатое суффиксное дерево|...») |
Genyaz (обсуждение | вклад) (Добавлена историческая справка) |
||
Строка 1: | Строка 1: | ||
{{В разработке}} | {{В разработке}} | ||
'''Алгоритм МакКрейта''' — алгоритм построения [[Сжатое суффиксное дерево|суффиксного дерева]] для заданной строки <tex>s</tex> за линейное время. Отличается от [[Алгоритм Укконена| алгоритма Укконена]] тем, что добавляет суффиксы в порядке убывания длины. | '''Алгоритм МакКрейта''' — алгоритм построения [[Сжатое суффиксное дерево|суффиксного дерева]] для заданной строки <tex>s</tex> за линейное время. Отличается от [[Алгоритм Укконена| алгоритма Укконена]] тем, что добавляет суффиксы в порядке убывания длины. | ||
+ | |||
+ | ==Историческая справка== | ||
+ | Первым оптимальным по времени был алгоритм, предложенный Вайнером в 1973 году. Идея алгоритма была в нахождении первых символов суффикса, которые находились в уже построенном дереве. Суффиксы просматривались от самого короткого к самому длинному, а для быстрого поиска использовались по два массива размера алфавита на каждую вершину, что затрудняло как понимание алгоритма, так и его реализацию и эффективность, особенно в плане занимаемой памяти. МакКрейт в 1976 году предложил [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.130.8022&rep=rep1&type=pdf свой алгоритм], в котором порядок добавления суффиксов заменен на обратный, а для быстрого вычисления места, откуда нужно продолжить построение нового суффикса, достаточно суффиксной ссылки в каждой вершине. В 1995 году Укконен представил [http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf свою версию алгоритма], которая считается наиболее простой для понимания, а также, в отличие от алгоритмов Вейнера и МакКрейта, является online алгоритмом, способным строить неявное суффиксное дерево по мере прочтения строки, а затем превратить его в настоящее. | ||
== Теоретическое обоснование == | == Теоретическое обоснование == | ||
− | Заметим, что если два суффикса имеют <tex>lcp</tex> (''largest common prefix'') общих символов, то в построенном суффиксном дереве они будут иметь [[Сведение задачи LCA к задаче RMQ| наименьшего общего предка]] на этой глубине. Будем рассматривать суффиксы в порядке убывания длины, тогда имеет смысл узнавать наибольшее <tex>lcp</tex> с новым суффиксом среди всех суффиксов, добавленных раньше. | + | Заметим, что если два суффикса имеют <tex>lcp</tex> (''largest common prefix'') общих символов, то в построенном суффиксном дереве они будут иметь [[Сведение задачи LCA к задаче RMQ| наименьшего общего предка]] на этой глубине. Будем рассматривать суффиксы в порядке убывания длины, тогда имеет смысл узнавать наибольшее <tex>lcp</tex> с новым суффиксом среди всех суффиксов, добавленных раньше. Обозначим как <tex>head_i</tex> {{---}} максимальный префикс <tex>s[j..\left\vert s \right\vert]</tex> и <tex>s[i..\left\vert s \right\vert]</tex> среди всех <tex>j < i</tex>. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | Пусть мы знаем <tex>head_i</tex> и место в дереве, которое ему соответствует. Если позиция находится на ребре, разрежем его, а потом добавим новую вершину. | + | Пусть мы знаем <tex>head_i</tex> и место в дереве, которое ему соответствует. Если позиция находится на ребре, разрежем его, а потом добавим новую вершину. Считать <tex>head_i</tex> по определению было бы очень затруднительно, но существует способ значительно сократить вычисления. |
− | |||
− | Считать <tex>head_i</tex> по определению было бы очень затруднительно, но существует способ значительно сократить вычисления. | ||
{{Лемма | {{Лемма | ||
Строка 27: | Строка 23: | ||
Для удобства реализации вместе с корнем <tex>root</tex> создадим вспомогательную вершину <tex>superRoot</tex>, обладающую свойствами: | Для удобства реализации вместе с корнем <tex>root</tex> создадим вспомогательную вершину <tex>superRoot</tex>, обладающую свойствами: | ||
* <tex>root.suf = superRoot</tex> | * <tex>root.suf = superRoot</tex> | ||
− | * Для любого символа <tex>c</tex> из вершины <tex>superRoot</tex> есть ребро в | + | * Для любого символа <tex>c</tex> из вершины <tex>superRoot</tex> есть ребро в <tex>root</tex>. |
Будем поддерживать инвариант: | Будем поддерживать инвариант: | ||
Строка 48: | Строка 44: | ||
|statement=Инвариант алгоритма сохраняется | |statement=Инвариант алгоритма сохраняется | ||
|proof=Инвариант мог бы нарушиться только в случае, если бы не существовало вершины в суффиксной ссылке для <tex>head_{i-1}</tex>, но мы продолжили бы сканирование по ребру дальше и получили две вершины <tex>head_{i-1}.suf, head_i</tex> с неопределенными суффиксными ссылками. | |proof=Инвариант мог бы нарушиться только в случае, если бы не существовало вершины в суффиксной ссылке для <tex>head_{i-1}</tex>, но мы продолжили бы сканирование по ребру дальше и получили две вершины <tex>head_{i-1}.suf, head_i</tex> с неопределенными суффиксными ссылками. | ||
− | Покажем, что это невозможно. Рассмотрим, что значит, что <tex>head_{i-1}.suf</tex> остановилась посередине ребра. Это означает, что все суффиксы <tex>s[j..\left\vert s \right\vert], j < i - 1</tex>, которые дошли до этого места, имеют совпадающие символы, по определению <tex>head_{i-1}</tex> отличающиеся от символа суффикса <tex>s[i - 1..\left\vert s \right\vert]</tex>. Тогда и <tex>s[i..\left\vert s \right\vert]</tex> должен отличаться в этом символе, значит <tex>head_i = head_{i-1}.suf</tex>. | + | Покажем, что это невозможно. Рассмотрим, что значит, что <tex>head_{i-1}.suf</tex> остановилась посередине ребра. Это означает, что все суффиксы <tex>s[j..\left\vert s \right\vert], j < i - 1</tex>, которые дошли до этого места, имеют совпадающие следующие символы, по определению <tex>head_{i-1}</tex> отличающиеся от символа суффикса <tex>s[i - 1..\left\vert s \right\vert]</tex>. Тогда и <tex>s[i..\left\vert s \right\vert]</tex> должен отличаться в этом символе, значит <tex>head_i = head_{i-1}.suf</tex>. |
}} | }} | ||
Строка 61: | Строка 57: | ||
== Сравнение с другими алгоритмами == | == Сравнение с другими алгоритмами == | ||
− | + | В сравнении с алгоритмом Вайнера: | |
+ | * Преимущества: каждая вершина хранит только суффиксную ссылку, а не массивы размера алфавита. | ||
+ | * Недостатки: нет. | ||
+ | |||
+ | В сравнении с [[Алгоритм Укконена| алгоритмом Укконена]]: | ||
* Преимущества: мы строим суффиксное дерево в явной форме, что может облегчить понимание алгоритма. | * Преимущества: мы строим суффиксное дерево в явной форме, что может облегчить понимание алгоритма. | ||
* Недостатки: является offline алгоритмом, то есть требует для начала работы всю строку целиком. | * Недостатки: является offline алгоритмом, то есть требует для начала работы всю строку целиком. | ||
== Источники == | == Источники == | ||
− | * [http:// | + | * [[wikipedia:en:Suffix_tree| Suffix tree - Wikipedia]] |
− | * [http://users-cs.au.dk/cstorm/courses/StrAlg_f12/slides/suffix-tree-construction.pdf ''C. N. Storm'', McCreight's suffix tree construction algorithm] | + | * [http://www.academia.edu/3146231/Algorithms_on_strings_trees_and_sequences_computer_science_and_computational_biology ''Gusfield, Dan'' , Algorithms on Strings, Trees and Sequences: Computer Science and Computational Biology // Cambridge University Press, {{---}} 1999. {{---}} ISBN: 0-521-58519-8] |
+ | * [http://users-cs.au.dk/cstorm/courses/StrAlg_f12/slides/suffix-tree-construction.pdf ''C. N. Storm'', McCreight's suffix tree construction algorithm] | ||
+ | |||
== См. также == | == См. также == | ||
* [[Сжатое суффиксное дерево]] | * [[Сжатое суффиксное дерево]] | ||
* [[Алгоритм Укконена]] | * [[Алгоритм Укконена]] | ||
+ | |||
+ | [[Категория: Дискретная математика и алгоритмы]] |
Версия 13:34, 3 мая 2014
Алгоритм МакКрейта — алгоритм построения суффиксного дерева для заданной строки за линейное время. Отличается от алгоритма Укконена тем, что добавляет суффиксы в порядке убывания длины.
Содержание
Историческая справка
Первым оптимальным по времени был алгоритм, предложенный Вайнером в 1973 году. Идея алгоритма была в нахождении первых символов суффикса, которые находились в уже построенном дереве. Суффиксы просматривались от самого короткого к самому длинному, а для быстрого поиска использовались по два массива размера алфавита на каждую вершину, что затрудняло как понимание алгоритма, так и его реализацию и эффективность, особенно в плане занимаемой памяти. МакКрейт в 1976 году предложил свой алгоритм, в котором порядок добавления суффиксов заменен на обратный, а для быстрого вычисления места, откуда нужно продолжить построение нового суффикса, достаточно суффиксной ссылки в каждой вершине. В 1995 году Укконен представил свою версию алгоритма, которая считается наиболее простой для понимания, а также, в отличие от алгоритмов Вейнера и МакКрейта, является online алгоритмом, способным строить неявное суффиксное дерево по мере прочтения строки, а затем превратить его в настоящее.
Теоретическое обоснование
Заметим, что если два суффикса имеют наименьшего общего предка на этой глубине. Будем рассматривать суффиксы в порядке убывания длины, тогда имеет смысл узнавать наибольшее с новым суффиксом среди всех суффиксов, добавленных раньше. Обозначим как — максимальный префикс и среди всех .
(largest common prefix) общих символов, то в построенном суффиксном дереве они будут иметьПусть мы знаем
и место в дереве, которое ему соответствует. Если позиция находится на ребре, разрежем его, а потом добавим новую вершину. Считать по определению было бы очень затруднительно, но существует способ значительно сократить вычисления.Лемма: |
Пусть , тогда — префикс . |
Доказательство: |
|
Если нам известны суффиксные ссылки
для каждой вершины , мы можем быстро перейти от позиции к ее суффиксу и продолжить сравнение символов оттуда. Если бы новая позиция всегда оказывалась существующей вершиной построенного дерева, этот алгоритм бы уже работал, но в реальности можно оказаться на середине ребра, для которой суффиксная ссылка неизвестна. Для нахождения ее суффиксной ссылки на следующей итерации мы сначала перейдем к предку, пройдем по суффиксной ссылке, а уже затем будем продолжать сравнение.Алгоритм
Для удобства реализации вместе с корнем
создадим вспомогательную вершину , обладающую свойствами:- Для любого символа из вершины есть ребро в .
Будем поддерживать инвариант:
- Для всех вершин, кроме, возможно, последней добавленной , известны суффиксные ссылки.
- Суффиксная ссылка всегда ведет в вершину, а не в середину ребра.
При добавлении каждого следующего суффикса будем выполнять следующие шаги:
- Если суффиксная ссылка
- Поднимемся вверх к ее предку;
- Пройдем по суффиксной ссылке;
- Спустимся вниз на столько символов, сколько мы прошли вверх к предку (fast scanning).
- Если мы оказались посередине ребра, разрежем его и добавим вершину.
- Установим суффиксную ссылку для
не определена:
- Иначе просто пройдем по суффиксной ссылке.
- Будем идти по дереву вниз, пока либо не будет перехода по символу, либо очередной символ на ребре не совпадет с символом нового суффикса (slow scanning)
- Добавим ребро/разрежем существующее, запомним новую позицию и добавим оставшуюся часть суффикса в качестве листа.
Утверждение: |
Инвариант алгоритма сохраняется |
Инвариант мог бы нарушиться только в случае, если бы не существовало вершины в суффиксной ссылке для Покажем, что это невозможно. Рассмотрим, что значит, что , но мы продолжили бы сканирование по ребру дальше и получили две вершины с неопределенными суффиксными ссылками. остановилась посередине ребра. Это означает, что все суффиксы , которые дошли до этого места, имеют совпадающие следующие символы, по определению отличающиеся от символа суффикса . Тогда и должен отличаться в этом символе, значит . |
Асимптотическая оценка
В приведенном алгоритме используется константное число операций на добавление одного суффикса, не считая slow scanning и fast scanning.
Slow scanning делает
операций, что суммарно дает операций.Fast scanning работает с целыми ребрами, поэтому будем использовать в качестве потенциала глубину в вершинах. Из структуры суффиксного дерева мы знаем, что суффиксная ссылка может уменьшить глубину вершины не более, чем на , так что мы на каждой итерации поднимаемся не более, чем на — один раз к предку, а потом по суффиксной ссылке, что составляет за весь алгоритм. Соответственно, спустимся мы тоже суммарно раз, так как и максимальная глубина составляет .
Итоговая асимптотика алгоритма —
.Сравнение с другими алгоритмами
В сравнении с алгоритмом Вайнера:
- Преимущества: каждая вершина хранит только суффиксную ссылку, а не массивы размера алфавита.
- Недостатки: нет.
В сравнении с алгоритмом Укконена:
- Преимущества: мы строим суффиксное дерево в явной форме, что может облегчить понимание алгоритма.
- Недостатки: является offline алгоритмом, то есть требует для начала работы всю строку целиком.
Источники
- Suffix tree - Wikipedia
- Gusfield, Dan , Algorithms on Strings, Trees and Sequences: Computer Science and Computational Biology // Cambridge University Press, — 1999. — ISBN: 0-521-58519-8
- C. N. Storm, McCreight's suffix tree construction algorithm