Построение суффиксного массива с помощью стандартных методов сортировки

Материал из Викиконспекты
Версия от 14:49, 17 мая 2011; 192.168.0.2 (обсуждение) (Построение суффиксного массива)
Перейти к: навигация, поиск

Идея построения суффиксного массива

Согласно определению суффиксного массива, для его построения достаточно отсортировать все суффиксы строки. Заменим сортировку суффиксов строки [math]\alpha[/math] на сортировку циклических сдвигов строки [math]\alpha\$[/math], где символ [math]\$[/math] строго меньше любого символа из [math]\alpha[/math]. Тогда если в упорядоченных циклических сдвигах отбросить суффикс, начинающийся на [math]\$[/math], то получим упорядоченные суффиксы исходной строки [math]\alpha[/math]. В дальнейшем положим [math]|\alpha\$| = N [/math] (заметим, что все циклические сдвиги также длины [math]N[/math]), а также [math]\alpha\$ = s[/math].

Алгоритм за O(N^2 log(N)) (наивно)

Данный алгоритм достаточно тривиален. Отсортируем все циклические сдвиги строки [math]\alpha\$[/math] воспользовавшись любым известным ранее методом логарифмической сортировки (например "сортировка слиянием"). Тогда время на сравнение любых двух циклических сдвигов будет осуществляться за [math]O(N)[/math] и суммарная сложность алгоритмы составит [math]O(N^2\log(N))[/math].

Алгоритм за O(N log^2(N)) (хэши)

Данный алгоритм является некоторым улучшением предыдущего. Основная цель - сократить оценку времени сравнения двух циклических сдвигов до [math]O(log(n))[/math], тогда мы по аналогии с предыдущим алгоритмом получим оценку [math]O(N log^2(N))[/math]. Для этого вычислим хэши всех префиксов строки [math]\alpha\$[/math] за [math]O(N)[/math]. Теперь у нас есть возможность проверять на равенство любые две подстроки (правда с определенной вероятностью мы можем получить неверный ответ на запрос).

Далее пусть нам необходимо сравнить два циклических сдвига [math]s[i_1..i_1-1][/math] и [math]s[i_2..i_2-1][/math]. Найдем сначала их наибольший общий префикс ([math]lcp(i_1,i_2)[/math]), для этого будем использовать двоичный поиск по длине совпадающего префикса, а проверку осуществлять с помощью посчитанных хэшей префиксов.


Если оказалось, что [math]lcp(i_1,i_2) = N[/math], то строки равны. Если же [math]lcp(i_1,i_2) \lt N[/math], то символы [math]s[i_1 + lcp][/math] и [math]s[i_2+lcp][/math] точно различаются, их сравнение позволяет сделать вывод, какой из циклических сдвигов меньше в лексикографическом порядке. И так двоичный поиск работает за [math]O(log(N))[/math] остальные операции требуют константного времени, получаем оценку времени, необходимого на сравнение двух циклических сдвигов [math]O(log(N))[/math].

Алгоритм за O(N log^2(N)) (префиксы циклических сдвигов)

Этот алгоритм сильно отличается от двух предыдущих и от него не сложно перейти к алгоритму за [math]O(N log(N))[/math]. И так основная идея: на каждом шаге будем сортировать префиксы циклических сдвигов длины [math]1,2,4,..., 2^{\lceil log_2(n)\rceil}[/math]. Еще одно важное дополнение: после каждой фазы, каждому префиксу циклического сдвига [math]s[i..i-1][/math] будет присваиваться номер класса эквивалентности [math]c[i][/math] среди этих префиксов. Причем классы эквивалентности должны быть пронумерованы в лексикографическом порядке соответствующих представителей.

В начале легко можно отсортировать за [math]O(N log(N))[/math] префиксы длины [math]1[/math], т.е. символы. А номера классов поставить в соответствии с порядковым номером символа в алфавите.

Рассмотрим теперь переход от префиксов длины [math]l[/math] к префиксам длины [math]2l[/math]. Научимся сравнивать два префикса длины [math]2l[/math] за [math]O(1)[/math]: Пусть даны префиксы [math]s[i..i+2l-1][/math], [math]s[j..j+2l-1][/math], сравним сначала их левые половинки, использовав значения [math]c[i], c[j][/math] с предыдущего шага, если [math]c[i]\neq c[j][/math], то префиксы соотносятся так как же, как [math]c[i][/math] и [math] c[j][/math], если [math]c[i]=c[j][/math], то переходим к сравнению [math]c[i+l][/math] и [math] c[j+l][/math]. И так отсортировать префиксы длины [math]2l[/math] можно за [math]O(Nlog(n))[/math]. Вычислить новые [math]c[i][/math] можно легко просто пробежавшись в лексикографическом порядке по префиксам, и увеличивая значение соответствующего класса на [math]1[/math] если текущий префикс не совпадает с предыдущим (сравнивать с помощью старых [math]c[i], c[i+l][/math]).

После шага [math]l =2^{\lceil log_2(n)\rceil} \ge N[/math]. Все циклические сдвиги будут отсортированы. Всего шагов [math]O(log(N))[/math], каждый шаг проводится за [math]O(N log(n))[/math], итоговая асимптотика [math]O(N log^2(N))[/math].