Алгоритм цифровой сортировки суффиксов циклической строки
НЕТ ВОЙНЕ |
24 февраля 2022 года российское руководство во главе с Владимиром Путиным развязало агрессивную войну против Украины. В глазах всего мира это военное преступление совершено от лица всей страны, всех россиян. Будучи гражданами Российской Федерации, мы против своей воли оказались ответственными за нарушение международного права, военное вторжение и массовую гибель людей. Чудовищность совершенного преступления не оставляет возможности промолчать или ограничиться пассивным несогласием. Мы убеждены в абсолютной ценности человеческой жизни, в незыблемости прав и свобод личности. Режим Путина — угроза этим ценностям. Наша задача — обьединить все силы для сопротивления ей. Эту войну начали не россияне, а обезумевший диктатор. И наш гражданский долг — сделать всё, чтобы её остановить. Антивоенный комитет России |
Распространяйте правду о текущих событиях, оберегайте от пропаганды своих друзей и близких. Изменение общественного восприятия войны - ключ к её завершению. |
meduza.io, Популярная политика, Новая газета, zona.media, Майкл Наки. |
Задача: |
Дана циклическая строка | . Суффиксом циклической строки называется строка (будем называть такую строку суффиксом под номером ). Требуется отсортировать все её суффиксы в порядке лексикографической сортировки.
Решение
Рассматриваемый алгоритм состоит из
итераций. На -той итерации ( ) сортируются циклические подстроки длины . На последней, -ой итерации, будут сортироваться подстроки длины , что эквивалентно сортировке циклических сдвигов.На каждой итерации будем хранить массив перестановки
, где — номер суффикса, занимающего позицию в текущей перестановке. Также будем хранить массив классов эквивалентности , где — класс эквивалентности, которому принадлежит префикс длины суффикса под номером . При этом если префикс суффикса под номером лексикографически меньше префикса суффикса под номером , то . Если же префиксы равны, то и их классы эквивалентности одинаковы. Так как мы вставили в строку символ , то к концу алгоритма каждый суффикс будет иметь уникальный класс эквивалентности, значит, мы установим порядок суффиксов.Описание алгоритма
На нулевой итерации отсортируем циклические подстроки длины сортировки подсчетом построим массив , содержащий номера суффиксов, отсортированных в лексикографическом порядке. По этому массиву построим массив классов эквивалентности .
, т.е. первые символы строк, и разделим их на классы эквивалентности (одинаковые символы должны быть отнесены к одному классу эквивалентности). При помощиНа
-ом проходе имеем массивы и , вычисленные на предыдущей итерации. Приведем алгоритм, выполняющий -ый проход за . Поскольку итераций всего , такой алгоритм имеет асимптотику .Заметим, что циклическая подстрока длины
состоит из двух подстрок длины , которые мы можем сравнивать между собой за , используя информацию с предыдущей итерации — номера классов эквивалентности . Таким образом, для подстроки длины , начинающейся в позиции , вся необходимая информация содержится в паре чисел .Отсортируем подстроки длины цифровая сортировка: отсортируем пары сначала по вторым элементам, а затем по первым (устойчивой сортировкой). Однако вторые элементы уже упорядочены — этот порядок задан в массиве от предыдущей итерации. Тогда, чтобы получить порядок пар по вторым элементам, надо от каждого элемента массива отнять ( даёт упорядочение подстрок длины , и при переходе к строке вдвое большей длины эти подстроки становятся их вторыми половинками, поэтому от позиции второй половинки отнимается длина первой половинки).
по данным парам и запишем порядок в массив . Воспользуемся здесь приёмом, на котором основанаЧтобы произвести устойчивую сортировку по первым элементам пар, воспользуемся сортировкой подсчетом, имеющую асимптотику
.Осталось пересчитать номера классов эквивалентности
, пройдя по новой перестановке и сравнивая соседние элементы (как пары двух чисел).Пример
0 фаза | 1 фаза | 2 фаза | 3 фаза | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
p | s | c | p | c | p | c | p | c | ||||||
7 | $ | 1 | 7 | $a | 1,2 | 1 | 7 | $aba | 1,5 | 1 | 7 | $abacaba | 1,8 | 1 |
0 | a | 2 | 6 | a$ | 2,1 | 2 | 6 | a$ab | 2,3 | 2 | 6 | a$abacab | 2,5 | 2 |
2 | a | 2 | 0 | ab | 2,3 | 3 | 4 | aba$ | 3,2 | 3 | 4 | aba$abac | 3,4 | 3 |
4 | a | 2 | 4 | ab | 2,3 | 3 | 0 | abac | 3,4 | 4 | 0 | abacaba$ | 4,3 | 4 |
6 | a | 2 | 2 | ac | 2,4 | 4 | 2 | acab | 4,3 | 5 | 2 | acaba$ab | 5,2 | 5 |
1 | b | 3 | 1 | ba | 3,1 | 5 | 5 | ba$a | 5,1 | 6 | 5 | ba$abaca | 6,7 | 6 |
5 | b | 3 | 5 | ba | 3,1 | 5 | 1 | baca | 5,6 | 7 | 1 | bacaba$a | 7,6 | 7 |
3 | c | 4 | 3 | ca | 4,1 | 6 | 3 | caba | 6,5 | 8 | 3 | caba$aba | 8,1 | 8 |
Псевдокод
/* преобразует масив count, так что теперь он содержит позиции в массиве suffs с которых необходимо вставлять подстроки, начинающиеся с соответствующих символов */ int[] calc_positions(int[] count) count[0] = 0 for i = 2 .. count.length - 1 count[i] += count[i - 1] return count /* принимает строку, для которой требуется построить суффиксный массив возвращает суффиксный массив */ int[] suff_array(string str) s += '$' // нулевая итерация count = int[max(, str.length)] fill(count, 0) for ch in str count[ch]++ count = 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 cur_len = 1 while cur_len str.length // сортируем по второй половине подстроки sorted_by2 = int[str.length] for i = 0 .. str.length - 1 sorted_by2[i] = (suffs[i] + str.length - cur_len) mod str.length // сортируем по первой половине // сортировка устойчивая, значит получим целиком отсортированные подстроки fill(count, 0) for by2 in sorted_by2 count[classes[by2]]++ count = calc_positions(count) for i = 0 .. str.length - 1 suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i] new_classes = int[str.length] classesN = 0 for i = 0 .. str.length - 1 mid1 = (suffs[i] + cur_len) mod str.length mid2 = (suffs[i - 1] + cur_len) mod 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