Алгоритм Карккайнена-Сандерса — различия между версиями
| Строка 1: | Строка 1: | ||
Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения [[суффиксный массив | суффиксного массива]] за линейное время. | Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения [[суффиксный массив | суффиксного массива]] за линейное время. | ||
| + | |||
| + | {{Определение | ||
| + | |definition= | ||
| + | '''Четным суффиксом''' назовем суффикс, начинающийся в четной позиции. <br> | ||
| + | '''Нечетным суффиксом''' — суффикс, начинающийся в нечетной позиции. | ||
| + | }} | ||
== Базовая идея == | == Базовая идея == | ||
Алгоритм базируется на алгоритме Фараха<ref>M. Farach. Optimal suffix tree construction with large alphabets. http://www.cs.rutgers.edu/~farach/pubs/FarFerrMuthu00.pdf </ref> построения суффиксного дерева за линейное время: | Алгоритм базируется на алгоритме Фараха<ref>M. Farach. Optimal suffix tree construction with large alphabets. http://www.cs.rutgers.edu/~farach/pubs/FarFerrMuthu00.pdf </ref> построения суффиксного дерева за линейное время: | ||
| − | # Строим суффиксное дерево для суффиксов | + | # Строим суффиксное дерево для четных суффиксов рекурсивно сведя задачу к построению суффиксного дерева для строки половинной длины. |
| − | # Строим суффиксное дерево для суффиксов | + | # Строим суффиксное дерево для нечетных суффиксов за линейное время, используя результат для четных позиций. |
# Сливаем суффиксные деревья за линейное время. | # Сливаем суффиксные деревья за линейное время. | ||
| Строка 10: | Строка 16: | ||
== Алгоритм == | == Алгоритм == | ||
| + | Для упрощения алгоритма вначале дополним нашу строку до четной длины (например, добавлением $ в конец). | ||
=== Шаг 1 === | === Шаг 1 === | ||
На первом шаге мы строим суффиксный массив <tex> A_{S_e} </tex> для суффиксов строки <tex> S </tex>, начинающихся в четных позициях. | На первом шаге мы строим суффиксный массив <tex> A_{S_e} </tex> для суффиксов строки <tex> S </tex>, начинающихся в четных позициях. | ||
# Отобразим исходную строку <tex> S </tex> длины <tex> n </tex> в строку <tex> S' </tex> длины <tex> \frac{n}{2} </tex> следующим образом: | # Отобразим исходную строку <tex> S </tex> длины <tex> n </tex> в строку <tex> S' </tex> длины <tex> \frac{n}{2} </tex> следующим образом: | ||
| − | #* Сделаем список, состоящий из пар символов вида <tex> S[2i]S[2i + 1] </tex>, где <tex> i \in [0; n / 2) </tex>. | + | #* Сделаем список, состоящий из пар символов вида <tex> S[2i]S[2i + 1] </tex>, где <tex> i \in [0; n / 2) </tex>. |
#* Отсортируем его цифровой сортировкой за линейное время и получим новый алфавит <tex> \Sigma' </tex>. | #* Отсортируем его цифровой сортировкой за линейное время и получим новый алфавит <tex> \Sigma' </tex>. | ||
#* Перекодируем строку <tex> S </tex> в алфавит <tex> \Sigma' </tex>, получив строку <tex> S' </tex> половинной длины. | #* Перекодируем строку <tex> S </tex> в алфавит <tex> \Sigma' </tex>, получив строку <tex> S' </tex> половинной длины. | ||
| Строка 21: | Строка 28: | ||
=== Шаг 2 === | === Шаг 2 === | ||
| − | На этом шаге мы за линейное время получим суффиксный массив <tex> A_{S_o} </tex> для суффиксов строки, начинающихся в нечетных позициях, используя уже построенный <tex> A_{S_e} </tex>. | + | На этом шаге мы за линейное время получим суффиксный массив <tex> A_{S_o} </tex> для суффиксов строки, начинающихся в нечетных позициях, используя уже построенный <tex> A_{S_e} </tex>. |
| + | |||
| + | Заметим, что сортировка множества нечетных суффиксов <tex> \{ S[i..n] | i \mod 2 == 1 \} </tex> аналогична сортировке множества пар <tex> \{ (S[i], S[i+1..n]) | i \mod 2 == 1 \} </tex>. Однако <tex> S[i+1..n] </tex> — четный суффикс, и его относительную позицию мы уже узнали на шаге 1, то есть свели задачу к сортировке <tex> \{ (S[i], pos_e[i+1]) | i \mod 2 == 1 \} </tex>, где <tex> pos_e[i] </tex> — позиция суффикса <tex> S[i..n] </tex> в <tex>A_{S_e} </tex>. | ||
| + | |||
| + | Таким образом, чтобы отсортировать эти пары за линейное время, сначала сразу выпишем их в порядке возрастания второго элемента пары (то есть в порядке вхождения в массив <tex> A_{S_e} </tex>), а потом отсортируем устойчивой сортировкой подсчетом по первым элементам. Так была потребована четность длины строки, последним суффиксом будет нечетный, ему будет соответствовать пара <tex> (S[n-1], n) </tex>. Псевдокод этого шага: | ||
| + | M = [] | ||
| + | M.add(Pair(S[n-1], n)) | ||
| + | for i = 0..n/2 - 1: | ||
| + | if <tex> A_{S_e}[i] </tex> == 0: //перед первым положительным суффиксом ничего не может стоять, поэтому пропускаем его | ||
| + | continue | ||
| + | else: | ||
| + | M.add(Pair(S[<tex> A_{S_e}[i] </tex>-1], <tex> A_{S_e}[i]</tex>)) | ||
| + | |||
| + | Заметим, что массив <tex> M </tex> явно не отсортирован по вторым элементам, но главное — что он отсортирован по возрастанию соответствующих вторым элементам суффиксам. После устойчивой сортировки массива <tex> M </tex> подсчетом по первому элементу легко восстановить массив <tex> A_{S_o} </tex>: | ||
| + | <tex> A_{S_o} </tex> = [] | ||
| + | for i = 0..n/2 - 1: | ||
| + | <tex> A_{S_o} </tex>.add(M[i].second - 1) | ||
| + | |||
| + | |||
| + | Получили, что весь второй шаг требует <tex> O(n) </tex> времени. | ||
=== Шаг 3 === | === Шаг 3 === | ||
Версия 20:45, 28 марта 2012
Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения суффиксного массива за линейное время.
| Определение: |
| Четным суффиксом назовем суффикс, начинающийся в четной позиции. Нечетным суффиксом — суффикс, начинающийся в нечетной позиции. |
Базовая идея
Алгоритм базируется на алгоритме Фараха[1] построения суффиксного дерева за линейное время:
- Строим суффиксное дерево для четных суффиксов рекурсивно сведя задачу к построению суффиксного дерева для строки половинной длины.
- Строим суффиксное дерево для нечетных суффиксов за линейное время, используя результат для четных позиций.
- Сливаем суффиксные деревья за линейное время.
Получили асимптотическое уравнение , решением которого является .
Алгоритм
Для упрощения алгоритма вначале дополним нашу строку до четной длины (например, добавлением $ в конец).
Шаг 1
На первом шаге мы строим суффиксный массив для суффиксов строки , начинающихся в четных позициях.
- Отобразим исходную строку длины в строку длины следующим образом:
- Сделаем список, состоящий из пар символов вида , где .
- Отсортируем его цифровой сортировкой за линейное время и получим новый алфавит .
- Перекодируем строку в алфавит , получив строку половинной длины.
- Рекурсивно построим суффиксный массив .
- Построим суффиксный массив . Очевидно, , так отношение упорядоченности любых двух строк в старом алфавите эквивалентно отношению упорядоченности в новом алфавите по его построению.
Шаг 2
На этом шаге мы за линейное время получим суффиксный массив для суффиксов строки, начинающихся в нечетных позициях, используя уже построенный .
Заметим, что сортировка множества нечетных суффиксов аналогична сортировке множества пар . Однако — четный суффикс, и его относительную позицию мы уже узнали на шаге 1, то есть свели задачу к сортировке , где — позиция суффикса в .
Таким образом, чтобы отсортировать эти пары за линейное время, сначала сразу выпишем их в порядке возрастания второго элемента пары (то есть в порядке вхождения в массив ), а потом отсортируем устойчивой сортировкой подсчетом по первым элементам. Так была потребована четность длины строки, последним суффиксом будет нечетный, ему будет соответствовать пара . Псевдокод этого шага:
M = []
M.add(Pair(S[n-1], n))
for i = 0..n/2 - 1:
if == 0: //перед первым положительным суффиксом ничего не может стоять, поэтому пропускаем его
continue
else:
M.add(Pair(S[-1], ))
Заметим, что массив явно не отсортирован по вторым элементам, но главное — что он отсортирован по возрастанию соответствующих вторым элементам суффиксам. После устойчивой сортировки массива подсчетом по первому элементу легко восстановить массив :
= [] for i = 0..n/2 - 1: .add(M[i].second - 1)
Получили, что весь второй шаг требует времени.
Шаг 3
Для суффиксного дерева третий шаг алгоритма опирается на специфические особенности суффиксных деревьев, которые не присущи суффиксным массивам. В случае суффиксного массива слияние становится очень сложным [2]. Однако простой модификацией алгоритма можно значительно упростить его.
Алгоритм skew
Изменим изначальный алгоритм следующим образом:
- Построим суффиксный массив для суффиксов, соответствующих не кратным трем позициям. Рекурсивно сведем это к построению суффиксного массива для строки длиной в две трети исходной.
- Построим суффиксный массив для суффиксов, соответствующих кратных трем позициям, используя результат первого шага за линейное время.
- Сливаем эти суффиксные массивы в один за линейное время.
Получили асимптотическое уравнение , решением которого также является (это видно из того, что сумма геометрической прогрессии с основанием равна ).
TODO: впилить описание сливания
Ссылки
- ↑ M. Farach. Optimal suffix tree construction with large alphabets. http://www.cs.rutgers.edu/~farach/pubs/FarFerrMuthu00.pdf
- ↑ D. K. Kim, J. S. Sim, H. Park, and K. Park. Linear-time construction of suffix arrays. http://www.springerlink.com/content/568156021q45r320/