Алгоритм цифровой сортировки суффиксов циклической строки
Содержание
Постановка задачи
Дана циклическая строка
. Суффиксом циклической строки называется строка . Требуется отсортировать все её суффиксы в порядке лексикографической сортировки.Решение
Рассматриваемый алгоритм состоит из
итераций. На -той итерации ( ) сортируются циклические подстроки длины . На последней, -ой итерации, будут сортироваться подстроки длины , что эквивалентно сортировке циклических сдвигов.На каждой итерации алгоритм помимо перестановки
индексов циклических подстрок будет поддерживать для каждой циклической подстроки длиной , начинающейся в позиции , номер класса эквивалентности , которому эта подстрока принадлежит. В самом деле, среди подстрок могут быть одинаковые, и алгоритму понадобится информация об этом. Кроме того, номера классов эквивалентности будем давать таким образом, чтобы они сохраняли и информацию о порядке: если один суффикс меньше другого, то и номер класса он должен получить меньший.Описание алгоритма
На нулевой итерации мы должны отсортировать циклические подстроки длины
, т.е. первые символы строк, и разделить их на классы эквивалентности (одинаковые символы должны быть отнесены к одному классу эквивалентности). Это можно сделать сортировкой подсчетом. Для каждого символа посчитаем, сколько раз он встретился. Потом по этой информации восстановим массив . После этого проходом по массиву и сравнением символов строится массив .Пусть мы выполнили
-ю итерацию (т.е. вычислили значения массивов и для неё). Научимся за выполнять -ю итерацию. Поскольку итераций всего , это даст нам требуемый алгоритм с временем .Заметим, что циклическая подстрока длины
состоит из двух подстрок длины , которые мы можем сравнивать между собой за , используя информацию с предыдущей итерации — номера классов эквивалентности . Таким образом, для подстроки длины , начинающейся в позиции , вся необходимая информация содержится в паре чисел .Это даёт нам весьма простое решение: отсортировать подстроки длины цифровая сортировка: чтобы отсортировать пары, отсортируем их сначала по вторым элементам, а затем — по первым элементам (обязательно устойчивой сортировкой). Однако отдельно вторые элементы уже упорядочены — этот порядок задан в массиве от предыдущей итерации. Тогда, чтобы упорядочить пары по вторым элементам, надо просто от каждого элемента массива отнять — это даст нам порядок сортировки пар по вторым элементам ( даёт упорядочение подстрок длины , и при переходе к строке вдвое большей длины эти подстроки становятся их вторыми половинками, поэтому от позиции второй половинки отнимается длина первой половинки).
просто по этим парам чисел, это и даст нам требуемый порядок, т.е. массив . Воспользуемся здесь приёмом, на котором основанаТаким образом, мы производим сортировку по вторым элементам пар. Теперь надо произвести устойчивую сортировку по первым элементам пар, её уже можно выполнить за
с помощью сортировки подсчётом.Осталось только пересчитать номера классов эквивалентности
, просто пройдя по полученной новой перестановке и сравнивая соседние элементы (опять же, сравнивая как пары двух чисел).Пример
s = abacaba$
Псевдокод
/* преобразует масив count, так что теперь он содержит позиции в массиве suffs с которых необходимо вставлять подстроки, начинающиеся с соответствующих символов */ calc_positions(count) count[0] = 0 for i = 2 .. count.length count[i] += count[i - 1] /* принимает строку, для которой требуется построить суффиксный массив возвращает суффиксный массив */ suff_array(str) s += '$' ALPHABET = 255 // нулевая итерация count = int[max(ALPHABET, str.length)] fill(count, 0) for ch in str count[ch]++ calc_positions(count) // suffs будет хранить индексы начал отсортированных подстрок текущей длины suffs = int[str.length] for ch in str suffs[count[ch]++] = i; classes = int[str.length] classesN = 0 last_char = '$' for suf in suffs if s[suf] != last_char last_char = s[suf[i]] classesN++ classes[suf] = classesN; // нулевая итерация завершена // сортируем подстроки длиной 2 * cur_len = 2^k curr_len = 1 while cur_len <= str.length // сортируем по второй половине подстроки sorted_by2 = int[str.length] for i = 0 .. str.length if suffs[i] < cur_len sorted_by2[i] = suffs[i] + str.length() - cur_len else sorted_by2[i] = suffs[i] - cur_len // сортируем по первой половине // сортировка устойчивая, значит получим целиком отсортированные подстроки fill(count, 0) for by2 in sorted_by2 count[classes[by2]]++ calc_positions(count) for i = 0 .. str.length suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i] new_classes = int[str.length] classesN = 0 for i = 0 .. str.length mid1 = (suffs[i] + cur_len) % str.length mid2 = (suffs[i - 1] + cur_len) % str.length if classes[suffs[i]] != classes[suffs[i-1]] or classes[mid1] != classes[mid2] classesN new_classes[suffs[i]] = classesN classes = new_classes cur_len *= 2 return suffs