Алгоритм цифровой сортировки суффиксов циклической строки — различия между версиями
AKhimulya (обсуждение | вклад) (→Решение) |
AKhimulya (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
− | |||
{{Задача | {{Задача | ||
|definition = Дана циклическая строка <tex>s[0 .. n - 1]</tex>. Суффиксом циклической строки <tex>s</tex> называется строка <tex>s[i .. n - 1] + s[0 .. i - 1], i < n </tex> (будем называть такую строкую суффиксом под номером <tex>i</tex>). Требуется отсортировать все её суффиксы в порядке лексикографической сортировки. | |definition = Дана циклическая строка <tex>s[0 .. n - 1]</tex>. Суффиксом циклической строки <tex>s</tex> называется строка <tex>s[i .. n - 1] + s[0 .. i - 1], i < n </tex> (будем называть такую строкую суффиксом под номером <tex>i</tex>). Требуется отсортировать все её суффиксы в порядке лексикографической сортировки. | ||
Строка 7: | Строка 6: | ||
Рассматриваемый алгоритм состоит из <tex>\lceil\log n\rceil + 1</tex> итераций. На <tex>k</tex>-той итерации (<tex>k=0..\lceil\log n\rceil </tex>) сортируются циклические подстроки длины <tex>2^k</tex>. На последней, <tex>\lceil\log n\rceil</tex>-ой итерации, будут сортироваться подстроки длины <tex>2^{\lceil\log n\rceil} \geqslant n</tex>, что эквивалентно сортировке циклических сдвигов. | Рассматриваемый алгоритм состоит из <tex>\lceil\log n\rceil + 1</tex> итераций. На <tex>k</tex>-той итерации (<tex>k=0..\lceil\log n\rceil </tex>) сортируются циклические подстроки длины <tex>2^k</tex>. На последней, <tex>\lceil\log n\rceil</tex>-ой итерации, будут сортироваться подстроки длины <tex>2^{\lceil\log n\rceil} \geqslant n</tex>, что эквивалентно сортировке циклических сдвигов. | ||
− | На каждой итерации будем хранить массив перестановки <tex>p[0 .. n - 1]</tex>, где <tex>p[i]</tex> | + | На каждой итерации будем хранить массив перестановки <tex>p[0 .. n - 1]</tex>, где <tex>p[i]</tex> — номер суффикса, занимающего позицию <tex>i</tex> в текущей перестановке. Также будем хранить массив классов эквивалентности <tex>c[0 .. n - 1]</tex>, где <tex>c[i]</tex> — класс эквивалентности, которому принадлежит префикс длины <tex>2^k</tex> суффикса под номером <tex>p[i]</tex>. При этом если префикс суффикса под номером <tex>p[i]</tex> лексикографически меньше префикса суффикса под номером <tex>p[j]</tex>, то <tex>c[i] < c[j]</tex>. Если же префиксы равны, то и их классы эквивалентности одинаковы. Так как мы вставили в строку символ <tex>\$</tex>, то к концу алгоритма каждый суффикс будет иметь уникальный класс эквивалентности, значит, мы установим порядок суффиксов. |
== Описание алгоритма == | == Описание алгоритма == | ||
Строка 51: | Строка 50: | ||
'''int[]''' calc_positions('''int[]''' count) | '''int[]''' calc_positions('''int[]''' count) | ||
count[0] = 0 | count[0] = 0 | ||
− | '''for''' i = 2 .. count.length | + | '''for''' i = 2 .. count.length - 1 |
count[i] += count[i - 1] | count[i] += count[i - 1] | ||
'''return''' count | '''return''' count | ||
Строка 69: | Строка 68: | ||
suffs = '''int'''[str.length] | suffs = '''int'''[str.length] | ||
'''for''' ch '''in''' str | '''for''' ch '''in''' str | ||
− | suffs[count[ch]++] = i | + | suffs[count[ch]++] = i |
classes = '''int'''[str.length] | classes = '''int'''[str.length] | ||
classesN = 0 | classesN = 0 | ||
Строка 77: | Строка 76: | ||
last_char = s[suf[i]] | last_char = s[suf[i]] | ||
classesN++ | classesN++ | ||
− | classes[suf] = classesN | + | classes[suf] = classesN |
<font color=darkgreen>// нулевая итерация завершена | <font color=darkgreen>// нулевая итерация завершена | ||
// сортируем подстроки длиной 2 * cur_len = 2^k</font> | // сортируем подстроки длиной 2 * cur_len = 2^k</font> | ||
− | + | cur_len = 1 | |
'''while''' cur_len <tex> \leqslant </tex> str.length | '''while''' cur_len <tex> \leqslant </tex> str.length | ||
<font color=darkgreen>// сортируем по второй половине подстроки</font> | <font color=darkgreen>// сортируем по второй половине подстроки</font> | ||
sorted_by2 = '''int'''[str.length] | sorted_by2 = '''int'''[str.length] | ||
− | '''for''' i = 0 .. str.length | + | '''for''' i = 0 .. str.length - 1 |
sorted_by2[i] = (suffs[i] + str.length - cur_len) '''mod''' str.length | sorted_by2[i] = (suffs[i] + str.length - cur_len) '''mod''' str.length | ||
<font color=darkgreen>// сортируем по первой половине | <font color=darkgreen>// сортируем по первой половине | ||
Строка 93: | Строка 92: | ||
count[classes[by2]]++ | count[classes[by2]]++ | ||
count = '''calc_positions'''(count) | count = '''calc_positions'''(count) | ||
− | '''for''' i = 0 .. str.length | + | '''for''' i = 0 .. str.length - 1 |
suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i] | suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i] | ||
new_classes = '''int'''[str.length] | new_classes = '''int'''[str.length] | ||
classesN = 0 | classesN = 0 | ||
− | '''for''' i = 0 .. str.length | + | '''for''' i = 0 .. str.length - 1 |
mid1 = (suffs[i] + cur_len) '''mod''' str.length | mid1 = (suffs[i] + cur_len) '''mod''' str.length | ||
mid2 = (suffs[i - 1] + cur_len) '''mod''' str.length | mid2 = (suffs[i - 1] + cur_len) '''mod''' str.length | ||
'''if''' classes[suffs[i]] <tex> \neq </tex> classes[suffs[i-1]] '''or''' classes[mid1] <tex> \neq </tex> classes[mid2] | '''if''' classes[suffs[i]] <tex> \neq </tex> classes[suffs[i-1]] '''or''' classes[mid1] <tex> \neq </tex> classes[mid2] | ||
− | classesN | + | classesN++ |
new_classes[suffs[i]] = classesN | new_classes[suffs[i]] = classesN | ||
classes = new_classes | classes = new_classes |
Версия 18:16, 2 июня 2015
Задача: |
Дана циклическая строка | . Суффиксом циклической строки называется строка (будем называть такую строкую суффиксом под номером ). Требуется отсортировать все её суффиксы в порядке лексикографической сортировки.
Решение
Рассматриваемый алгоритм состоит из
итераций. На -той итерации ( ) сортируются циклические подстроки длины . На последней, -ой итерации, будут сортироваться подстроки длины , что эквивалентно сортировке циклических сдвигов.На каждой итерации будем хранить массив перестановки
, где — номер суффикса, занимающего позицию в текущей перестановке. Также будем хранить массив классов эквивалентности , где — класс эквивалентности, которому принадлежит префикс длины суффикса под номером . При этом если префикс суффикса под номером лексикографически меньше префикса суффикса под номером , то . Если же префиксы равны, то и их классы эквивалентности одинаковы. Так как мы вставили в строку символ , то к концу алгоритма каждый суффикс будет иметь уникальный класс эквивалентности, значит, мы установим порядок суффиксов.Описание алгоритма
На нулевой итерации отсортируем циклические подстроки длины сортировки подсчетом построим массив , содержащий номера суффиксов, отсортированных в лексикографическом порядке. По этому массиву построим массив классов эквивалентности .
, т.е. первые символы строк, и разделим их на классы эквивалентности (одинаковые символы должны быть отнесены к одному классу эквивалентности). При помощиНа
-ом проходе имеем массивы и , вычисленные на предыдущей итерации. Приведем алгоритм, выполняющий -ую проход за . Поскольку итераций всего , такой алгоритм имеет асимптотику .Заметим, что циклическая подстрока длины
состоит из двух подстрок длины , которые мы можем сравнивать между собой за , используя информацию с предыдущей итерации — номера классов эквивалентности . Таким образом, для подстроки длины , начинающейся в позиции , вся необходимая информация содержится в паре чисел .Отсортируем подстроки длины цифровая сортировка: отсортируем пары сначала по вторым элементам, а затем по первым (устойчивой сортировкой). Однако вторые элементы уже упорядочены — этот порядок задан в массиве от предыдущей итерации. Тогда, чтобы получить порядок пар по вторым элементам, надо от каждого элемента массива отнять ( даёт упорядочение подстрок длины , и при переходе к строке вдвое большей длины эти подстроки становятся их вторыми половинками, поэтому от позиции второй половинки отнимается длина первой половинки).
по данным парам и запишем порядок в массив . Воспользуемся здесь приёмом, на котором основанаЧтобы произвести устойчивую сортировку по первым элементам пар, воспользуемся сортировкой подсчетом, имеющую асимптотику
.Осталось пересчитать номера классов эквивалентности
, пройдя по новой перестановке и сравнивая соседние элементы (как пары двух чисел).Пример
Псевдокод
/* преобразует масив 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