Алгоритм Карккайнена-Сандерса — различия между версиями
 (→Алгоритм)  | 
				|||
| Строка 1: | Строка 1: | ||
Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения [[суффиксный массив | суффиксного массива]] за линейное время.  | Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения [[суффиксный массив | суффиксного массива]] за линейное время.  | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
== Базовая идея ==  | == Базовая идея ==  | ||
| Строка 16: | Строка 10: | ||
== Алгоритм «разделяй и властвуй» ==    | == Алгоритм «разделяй и властвуй» ==    | ||
| − | Для упрощения алгоритма вначале дополним нашу строку до четной длины (например, добавлением $ в конец). На шаге   | + | {{Определение  | 
| + | |definition=  | ||
| + | '''Четным суффиксом''' назовем суффикс, начинающийся в четной позиции. <br>  | ||
| + | '''Нечетным суффиксом''' — суффикс, начинающийся в нечетной позиции.  | ||
| + | }}  | ||
| + | |||
| + | Для упрощения алгоритма вначале дополним нашу строку до четной длины (например, добавлением $ в конец). На шаге слияния мы сможем избавиться от него.  | ||
=== Шаг 1 ===  | === Шаг 1 ===  | ||
На первом шаге мы строим суффиксный массив <tex> A_{S_o} </tex> для нечетных суффиксов строки <tex> S </tex>.  | На первом шаге мы строим суффиксный массив <tex> A_{S_o} </tex> для нечетных суффиксов строки <tex> S </tex>.  | ||
| Строка 68: | Строка 68: | ||
Изменим изначальный алгоритм следующим образом:  | Изменим изначальный алгоритм следующим образом:  | ||
# Построим суффиксный массив для суффиксов, соответствующих не кратным трем позициям. Рекурсивно сведем это к построению суффиксного массива для строки длиной в две трети исходной.  | # Построим суффиксный массив для суффиксов, соответствующих не кратным трем позициям. Рекурсивно сведем это к построению суффиксного массива для строки длиной в две трети исходной.  | ||
| − | # Построим суффиксный массив для суффиксов, соответствующих   | + | # Построим суффиксный массив для суффиксов, соответствующих кратным трем позициям, используя результат первого шага за линейное время.  | 
# Сливаем эти суффиксные массивы в один за линейное время.  | # Сливаем эти суффиксные массивы в один за линейное время.  | ||
| Строка 78: | Строка 78: | ||
На этом шаге строится суффиксный массив <tex> A_{S_{12}} </tex> для множества суффиксов <tex> \{ S[i..n-1] | i \mod 3 \ne 0 \} </tex>.  | На этом шаге строится суффиксный массив <tex> A_{S_{12}} </tex> для множества суффиксов <tex> \{ S[i..n-1] | i \mod 3 \ne 0 \} </tex>.  | ||
# Получим строку <tex> S' </tex> аналогично предыдущему алгоритму:  | # Получим строку <tex> S' </tex> аналогично предыдущему алгоритму:  | ||
| − | #* Сделаем список, состоящий из троек <tex> S[i..i+2]  | + | #* Сделаем список, состоящий из троек <tex> S[i..i+2]</tex> , где <tex> i \mod 3 \ne 0 </tex>, причем примем <tex> S[n-2..n] = S[n-2..n-1]\$ </tex>, а <tex> S[n-1..n+1] = S[n-1]\$\$ </tex>.  | 
#* Отсортируем его за линейное время цифровой сортировкой и получим новый алфавит <tex> \Sigma' </tex>.  | #* Отсортируем его за линейное время цифровой сортировкой и получим новый алфавит <tex> \Sigma' </tex>.  | ||
#* Перекодируем строку <tex> S </tex> в строку <tex> S' </tex> длиной <tex> \frac23 n </tex> в алфавите <tex> \Sigma' </tex> следущим образом: <tex> S' = [ \Sigma'(s[i..i+2]) | i \mod 3 == 1 ] + [ \Sigma'(s[i..i+2]) | i \mod 3 == 2 ] </tex>. Суффиксу <tex> S[i..n-1] </tex> в старом алфавите, где <tex> i \mod 3 == 1 </tex>, в новом алфавите будет соответствовать строка <tex> S'[\frac{i-1}{3}..\frac{n}{3} - 1] </tex>, а если <tex> i \mod 3 == 2 </tex>, то строка <tex> S'[\frac{n}{3} + \frac{i-2}{3}..\frac{2n}{3} - 1] </tex>.  | #* Перекодируем строку <tex> S </tex> в строку <tex> S' </tex> длиной <tex> \frac23 n </tex> в алфавите <tex> \Sigma' </tex> следущим образом: <tex> S' = [ \Sigma'(s[i..i+2]) | i \mod 3 == 1 ] + [ \Sigma'(s[i..i+2]) | i \mod 3 == 2 ] </tex>. Суффиксу <tex> S[i..n-1] </tex> в старом алфавите, где <tex> i \mod 3 == 1 </tex>, в новом алфавите будет соответствовать строка <tex> S'[\frac{i-1}{3}..\frac{n}{3} - 1] </tex>, а если <tex> i \mod 3 == 2 </tex>, то строка <tex> S'[\frac{n}{3} + \frac{i-2}{3}..\frac{2n}{3} - 1] </tex>.  | ||
| Строка 97: | Строка 97: | ||
  for i = 0..2n/3 - 1:  |   for i = 0..2n/3 - 1:  | ||
      if <tex> A_{S_{12}}</tex>[i] % 3 == 1:  |       if <tex> A_{S_{12}}</tex>[i] % 3 == 1:  | ||
| − |           M.add(Pair(S[A_{S_{12}}</tex>[i] - 1], A_{S_{12}}</tex>[i]))  | + |           M.add(Pair(S[<tex>A_{S_{12}}</tex>[i] - 1], <tex>A_{S_{12}}</tex>[i]))  | 
  stable_sort(M)  |   stable_sort(M)  | ||
  for i = 0..n/3 - 1:  |   for i = 0..n/3 - 1:  | ||
| Строка 110: | Строка 110: | ||
Пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям <tex> i </tex>, равной 1 по модулю 3, и <tex> j </tex> (она всегда будет равна 0 по модулю 3). Это аналогично сравнению пар <tex> (S[i], S[i+1..n-1]) </tex> и <tex> (S[j], S[j+1..n-1]) </tex>. Сравнить первые элементы пар мы можем за <tex> O(1) </tex>, а относительный порядок вторых элементов пар нам уже известен, так как они соотвествуют позициям, равным 2 и 1 по модулю 3 соответственно.  | Пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям <tex> i </tex>, равной 1 по модулю 3, и <tex> j </tex> (она всегда будет равна 0 по модулю 3). Это аналогично сравнению пар <tex> (S[i], S[i+1..n-1]) </tex> и <tex> (S[j], S[j+1..n-1]) </tex>. Сравнить первые элементы пар мы можем за <tex> O(1) </tex>, а относительный порядок вторых элементов пар нам уже известен, так как они соотвествуют позициям, равным 2 и 1 по модулю 3 соответственно.  | ||
| − | Аналогично, пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям <tex> i </tex>, равной 2 по модулю 3, и <tex> j </tex> (она всегда будет равна 0 по модулю 3). Тогда это аналогично сравнению троек <tex> (S[i], S[i+1], S[i+2..n-1]) </tex> и <tex> (S[j], S[j+1], S[j+2..n-1]) </tex>, что   | + | Аналогично, пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям <tex> i </tex>, равной 2 по модулю 3, и <tex> j </tex> (она всегда будет равна 0 по модулю 3). Тогда это аналогично сравнению троек <tex> (S[i], S[i+1], S[i+2..n-1]) </tex> и <tex> (S[j], S[j+1], S[j+2..n-1]) </tex>, что также можно делать за <tex> O(1) </tex>.    | 
Псевдокод этой фазы:  | Псевдокод этой фазы:  | ||
  <tex>A_{S}</tex> = []  |   <tex>A_{S}</tex> = []  | ||
| − |   // Вначале предподсчитаем за O(n) обратные перестановки для суффиксных массивов, то есть массивы   | + |   // Вначале предподсчитаем за O(n) обратные перестановки для суффиксных массивов, то есть массивы order такие, что A[order[i]] = i.  | 
  // Тогда мы сможем за O(1) сравнивать суффиксы по их позиции.  |   // Тогда мы сможем за O(1) сравнивать суффиксы по их позиции.  | ||
| − | + |   order12 = inverse(<tex>A_{S_{12}}</tex>)    | |
| − | + |   order0  = inverse(<tex>A_{S_0}</tex>)  | |
  while i < 2 * n / 3 and j < n / 3:  |   while i < 2 * n / 3 and j < n / 3:  | ||
      pos12 = <tex> A_{S_{12}} </tex>[i]  |       pos12 = <tex> A_{S_{12}} </tex>[i]  | ||
      pos0  = <tex> A_{0} </tex>[j]  |       pos0  = <tex> A_{0} </tex>[j]  | ||
      if pos12 % 3 == 1:  |       if pos12 % 3 == 1:  | ||
| − |           if Pair(S[pos12],   | + |           if Pair(S[pos12], order12[pos12 + 1]) < Pair(S[pos0], order0[pos0 + 1]):  | 
              <tex>A_{S}</tex>.add(pos12)  |               <tex>A_{S}</tex>.add(pos12)  | ||
              i++  |               i++  | ||
| Строка 130: | Строка 130: | ||
              j++     |               j++     | ||
      else:  |       else:  | ||
| − |           if Triple(S[pos12], S[pos12 + 1],   | + |           if Triple(S[pos12], S[pos12 + 1], order12[pos12 + 2]) < Triple(S[pos0], S[pos0 + 1], order0[pos0 + 2]):  | 
              <tex>A_{S}</tex>.add(pos12)  |               <tex>A_{S}</tex>.add(pos12)  | ||
              i++  |               i++  | ||
| Строка 143: | Строка 143: | ||
     i++  |      i++  | ||
| − | Таким образом, получили простой метод   | + | Таким образом, получили простой метод слияния за <tex> O(n) </tex>.  | 
== Ссылки ==  | == Ссылки ==  | ||
Версия 23:00, 29 марта 2012
Алгоритм Каркайнена-Сандерса (Karkkainen, Sanders) — алгоритм построения суффиксного массива за линейное время.
Содержание
Базовая идея
Алгоритм базируется на алгоритме Фараха[1] построения суффиксного дерева за линейное время:
- Строим суффиксное дерево для четных суффиксов рекурсивно сведя задачу к построению суффиксного дерева для строки половинной длины.
 - Строим суффиксное дерево для нечетных суффиксов за линейное время, используя результат для четных позиций.
 - Сливаем суффиксные деревья за линейное время.
 
Получили асимптотическое уравнение , решением которого является .
Алгоритм «разделяй и властвуй»
| Определение: | 
| Четным суффиксом назовем суффикс, начинающийся в четной позиции.  Нечетным суффиксом — суффикс, начинающийся в нечетной позиции.  | 
Для упрощения алгоритма вначале дополним нашу строку до четной длины (например, добавлением $ в конец). На шаге слияния мы сможем избавиться от него.
Шаг 1
На первом шаге мы строим суффиксный массив для нечетных суффиксов строки .
-  Отобразим исходную строку  длины  в строку  длины  следующим образом:
- Сделаем список, состоящий из пар символов вида , где , причем обозначим как .
 - Отсортируем его цифровой сортировкой за линейное время и получим новый алфавит .
 - Перекодируем строку в алфавит , получив строку половинной длины.
 
 - Рекурсивно построим суффиксный массив .
 - Построим суффиксный массив . Очевидно, , так отношение упорядоченности любых двух строк в старом алфавите эквивалентно отношению упорядоченности в новом алфавите по его построению.
 
Шаг 2
На этом шаге мы за линейное время получим суффиксный массив для четных суффиксов, используя уже построенный .
Заметим, что сортировка множества четных суффиксов аналогична сортировке множества пар . Однако — нечетный суффикс, и его относительную позицию мы уже узнали на шаге 1.
Таким образом, чтобы отсортировать эти пары за линейное время, сначала сразу выпишем их в порядке возрастания второго элемента пары (то есть в порядке вхождения в массив ), а потом отсортируем устойчивой сортировкой подсчетом по первым элементам. Псевдокод этого шага:
M = []
for i = 0..n/2 - 1:
    M.add(Pair(S[[i] - 1], [i]))
Заметим, что массив явно не отсортирован по вторым элементам и хранит не суффиксы, а их позиции в строке , но главное — что он отсортирован по возрастанию соответствующих этим позициям нечетным суффиксам. После устойчивой сортировки массива подсчетом по первому элементу легко восстановить массив :
stable_sort(M) = [] for i = 0..n/2 - 1: .add(M[i].second - 1)
Получили, что весь второй шаг требует  времени.
Шаг 3
Для суффиксного дерева третий шаг алгоритма опирается на специфические особенности суффиксных деревьев, которые не присущи суффиксным массивам. В случае суффиксного массива слияние становится очень сложным [2]. Однако простой модификацией алгоритма можно значительно упростить его.
Пример
Покажем первые два шага агоритма для строки bbaaabab.
Во-первых, добавим защитный символ $, получив строку bbaaabab$. Во-вторых, дополним ее до четной длины, получив bbaaabab$$.
Шаг 1
- В новом алфавите будет четыре элемента — ba, aa, b$, $$. После сортировки они получат номера 3, 1, 2 и 0 соответственно.
 - Сжатой строкой будет 31320.
 - После рекурсивного вызова получим, что = [4, 1, 3, 0, 2], и = [9, 3, 7, 1, 5].
 
Шаг 2
- Обойдя массив , получим M = [($, 9), (a, 3), (a, 7), (b, 1), (a, 5)].
 - После сортировки подсчетом по первому элементу, получим M = [($, 9), (a, 3), (a, 7), (a, 5), (b, 1)].
 - Восстановив массив , получаем [8, 2, 6, 4, 0], что действительно является суффиксным массивом для четных суффиксов.
 
Алгоритм Каркайнена-Сандерса
Изменим изначальный алгоритм следующим образом:
- Построим суффиксный массив для суффиксов, соответствующих не кратным трем позициям. Рекурсивно сведем это к построению суффиксного массива для строки длиной в две трети исходной.
 - Построим суффиксный массив для суффиксов, соответствующих кратным трем позициям, используя результат первого шага за линейное время.
 - Сливаем эти суффиксные массивы в один за линейное время.
 
Получили асимптотическое уравнение , решением которого также является (это видно из того, что сумма геометрической прогрессии с основанием равна ).
Аналогично первой версии алгоритма, дополним строку до длины, кратной трем, защитными символами .
Шаг 1
На этом шаге строится суффиксный массив для множества суффиксов .
-  Получим строку  аналогично предыдущему алгоритму:
- Сделаем список, состоящий из троек , где , причем примем , а .
 - Отсортируем его за линейное время цифровой сортировкой и получим новый алфавит .
 - Перекодируем строку в строку длиной в алфавите следущим образом: . Суффиксу в старом алфавите, где , в новом алфавите будет соответствовать строка , а если , то строка .
 
 - Вызовем алгоритм рекурсивно для строки , получив суффиксный массив .
 - Пройдем по массиву . Если , то этот суффикс соответствует позиции в строке , если же , то этот суффикс соответствует позиции в строке . Псевдокод получения :
 
= [] for i = 0...length - 1: if [i] < n / 3: .add(3 * [i] + 1) else: .add(3 * ([i] - n / 3) + 2)
Шаг 2
Этот шаг также аналогичен первой версии алгоритма. Сортировка множества аналогична сортировке пар , где — суффиксы в позициях, равных 1 по модулю 3, относительный порядок которых уже известен. Выпишем эти пары в порядке вхождения их в и отсортируем по первому элементу устойчивой сортировкой подсчетом, получив суффиксный массив . Псевдокод этого шага:
= [] M = [] for i = 0..2n/3 - 1: if [i] % 3 == 1: M.add(Pair(S[[i] - 1], [i])) stable_sort(M) for i = 0..n/3 - 1: .add(M[i].second - 1)
Аналогично, второй шаг требует времени.
Шаг 3
На этом шаге мы должны слить суффиксные массивы и , чтобы получить суффиксный массив для всей строки .
Применим стандартный алгоритм слияния двух отсортированных массивов. Заметим, что явно массивы не отсортированы, но сотвествующие элементам массива суффиксы — отсортированы.
Пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям , равной 1 по модулю 3, и (она всегда будет равна 0 по модулю 3). Это аналогично сравнению пар и . Сравнить первые элементы пар мы можем за , а относительный порядок вторых элементов пар нам уже известен, так как они соотвествуют позициям, равным 2 и 1 по модулю 3 соответственно.
Аналогично, пусть на какой-то итерации слияния мы сравниваем суффиксы, соответствующие позициям , равной 2 по модулю 3, и (она всегда будет равна 0 по модулю 3). Тогда это аналогично сравнению троек и , что также можно делать за .
Псевдокод этой фазы:
= [] // Вначале предподсчитаем за O(n) обратные перестановки для суффиксных массивов, то есть массивы order такие, что A[order[i]] = i. // Тогда мы сможем за O(1) сравнивать суффиксы по их позиции. order12 = inverse() order0 = inverse() while i < 2 * n / 3 and j < n / 3: pos12 = [i] pos0 = [j] if pos12 % 3 == 1: if Pair(S[pos12], order12[pos12 + 1]) < Pair(S[pos0], order0[pos0 + 1]): .add(pos12) i++ else: .add(pos0) j++ else: if Triple(S[pos12], S[pos12 + 1], order12[pos12 + 2]) < Triple(S[pos0], S[pos0 + 1], order0[pos0 + 2]): .add(pos12) i++ else: .add(pos0) j++ while i < 2 * n / 3: .add([i]) i++ while j < n / 3: .add([j]) i++
Таким образом, получили простой метод слияния за .
Ссылки
- ↑ 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/