Алгоритм цифровой сортировки суффиксов циклической строки — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(langle & rangle вместо less & greater)
(Псевдокод)
Строка 42: Строка 42:
  
 
==Псевдокод==
 
==Псевдокод==
     /* преобразует масив count, так что
+
     <font color=darkgreen>/* преобразует масив count, так что
 
       теперь он содержит позиции в массиве suffs с которых  
 
       теперь он содержит позиции в массиве suffs с которых  
       необходимо вставлять подстроки, начинающиеся с соответствующих символов */
+
       необходимо вставлять подстроки, начинающиеся с соответствующих символов */</font>
     '''calc_positions'''(count)
+
     '''int[]''' calc_positions(count)
 
         count[0] = 0
 
         count[0] = 0
 
         '''for''' i = 2 .. count.length
 
         '''for''' i = 2 .. count.length
 
             count[i] += count[i - 1]
 
             count[i] += count[i - 1]
 +
        '''return''' count
 
        
 
        
     /* принимает строку, для которой требуется построить суффиксный массив
+
     <font color=darkgreen>/* принимает строку, для которой требуется построить суффиксный массив
       возвращает суффиксный массив */
+
       возвращает суффиксный массив */</font>
     '''suff_array'''(str)
+
     '''int[]''' suff_array(str)
 
         s += '$'
 
         s += '$'
         ALPHABET = 255
+
         ''ALPHABET'' = 255
 
          
 
          
         // нулевая итерация
+
         <font color=darkgreen>// нулевая итерация</font>
         count = '''int'''[max(ALPHABET, str.length)]
+
         count = '''int'''[max(''ALPHABET'', str.length)]
 
         '''fill'''(count, 0)
 
         '''fill'''(count, 0)
 
         '''for''' ch '''in''' str
 
         '''for''' ch '''in''' str
 
             count[ch]++
 
             count[ch]++
         '''calc_positions'''(count)
+
         count = '''calc_positions'''(count)
         // suffs будет хранить индексы начал отсортированных подстрок текущей длины
+
         <font color=darkgreen>// suffs будет хранить индексы начал отсортированных подстрок текущей длины</font>
 
         suffs = '''int'''[str.length]
 
         suffs = '''int'''[str.length]
 
         '''for''' ch '''in''' str
 
         '''for''' ch '''in''' str
Строка 75: Строка 76:
 
             classes[suf] = classesN;
 
             classes[suf] = classesN;
 
          
 
          
         // нулевая итерация завершена
+
         <font color=darkgreen>// нулевая итерация завершена
         // сортируем подстроки длиной 2 * cur_len = 2^k
+
         // сортируем подстроки длиной 2 * cur_len = 2^k</font>
 
         curr_len = 1
 
         curr_len = 1
         '''while''' cur_len <= str.length
+
         '''while''' cur_len <tex> \leqslant </tex> str.length
             // сортируем по второй половине подстроки
+
             <font color=darkgreen>// сортируем по второй половине подстроки</font>
 
             sorted_by2 = '''int'''[str.length]
 
             sorted_by2 = '''int'''[str.length]
 
             '''for''' i = 0 .. str.length
 
             '''for''' i = 0 .. str.length
 
                 sorted_by2[i] = (suffs[i] + str.length - cur_len) % str.length
 
                 sorted_by2[i] = (suffs[i] + str.length - cur_len) % str.length
             // сортируем по первой половине
+
             <font color=darkgreen>// сортируем по первой половине
             // сортировка устойчивая, значит получим целиком отсортированные подстроки
+
             // сортировка устойчивая, значит получим целиком отсортированные подстроки</font>
 
             '''fill'''(count, 0)
 
             '''fill'''(count, 0)
 
             '''for''' by2 '''in''' sorted_by2
 
             '''for''' by2 '''in''' sorted_by2
 
                 count[classes[by2]]++
 
                 count[classes[by2]]++
             '''calc_positions'''(count)
+
             count = '''calc_positions'''(count)
 
             '''for''' i = 0 .. str.length
 
             '''for''' i = 0 .. str.length
 
                 suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i]
 
                 suffs[count[classes[sorted_by2[i]]]++] = sorted_by2[i]

Версия 12:38, 31 мая 2015

Постановка задачи

Дана циклическая строка [math]s[0 .. n - 1][/math]. Суффиксом циклической строки [math]s[/math] называется строка [math]s[i .. n - 1] + s[0 .. i - 1], i \lt n [/math] (будем называть такую строкую суффиксом под номером i). Требуется отсортировать все её суффиксы в порядке лексикографической сортировки.

Решение

Рассматриваемый алгоритм состоит из [math]\lceil\log n\rceil + 1[/math] итераций. На [math]k[/math]-той итерации ([math]k=0..\lceil\log n\rceil [/math]) сортируются циклические подстроки длины [math]2^k[/math]. На последней, [math]\lceil\log n\rceil[/math]-ой итерации, будут сортироваться подстроки длины [math]2^{\lceil\log n\rceil} \ge n[/math], что эквивалентно сортировке циклических сдвигов.

На каждой итерации алгоритм помимо перестановки [math]p[0..n-1][/math] индексов циклических подстрок будет поддерживать для каждой циклической подстроки длиной [math]2^k[/math], начинающейся в позиции [math]i[/math], номер класса эквивалентности [math]c[i][/math], которому эта подстрока принадлежит. В самом деле, среди подстрок могут быть одинаковые, и алгоритму понадобится информация об этом. Кроме того, номера классов эквивалентности [math]c[i][/math] будем давать таким образом, чтобы они сохраняли и информацию о порядке: если один суффикс меньше другого, то и номер класса он должен получить меньший.

Описание алгоритма

На нулевой итерации отсортируем циклические подстроки длины [math]1[/math], т.е. первые символы строк, и разделим их на классы эквивалентности (одинаковые символы должны быть отнесены к одному классу эквивалентности). При помощи сортировки подсчетом построим массив [math]p[/math], содержащий номера суффиксов, отсортированных в лексикографическом порядке. По этому массиву построим массив классов эквивалентности [math]c[/math].

На [math]k[/math]-ой итерации имеем массивы [math]p[/math] и [math]c[/math], вычисленные на предыдущей итерации. Приведем алгоритм, выполняющий [math]k[/math]-ую итерацию за [math]O(n)[/math]. Поскольку итераций всего [math]O(\log n)[/math], такой алгоритм имеет асимптотику [math]O(n \log n)[/math].

Заметим, что циклическая подстрока длины [math]2^k[/math] состоит из двух подстрок длины [math]2^{k-1}[/math], которые мы можем сравнивать между собой за [math]O(1)[/math], используя информацию с предыдущей итерации — номера классов эквивалентности [math]c[/math]. Таким образом, для подстроки длины [math]2^k[/math], начинающейся в позиции [math]i[/math], вся необходимая информация содержится в паре чисел [math]\langle c[i], c[i + 2^{k-1}]\rangle [/math].

Отсортируем подстроки длины [math]2^k[/math] по данным парам и запишем порядок в массив [math]p[/math]. Воспользуемся здесь приёмом, на котором основана цифровая сортировка: отсортируем пары сначала по вторым элементам, а затем по первым (устойчивой сортировкой). Однако вторые элементы уже упорядочены — этот порядок задан в массиве от предыдущей итерации. Тогда, чтобы получить порядок пар по вторым элементам, надо от каждого элемента массива [math]p[/math] отнять [math]2^{k-1}[/math] ([math]p[/math] даёт упорядочение подстрок длины [math]2^{k-1}[/math], и при переходе к строке вдвое большей длины эти подстроки становятся их вторыми половинками, поэтому от позиции второй половинки отнимается длина первой половинки).

Чтобы произвести устойчивую сортировку по первым элементам пар, воспользуемся сортировкой подсчетом, имеющую асимптотику [math]O(n)[/math].

Осталось пересчитать номера классов эквивалентности [math]c[/math], пройдя по новой перестановке [math]p[/math] и сравнивая соседние элементы (как пары двух чисел).

Пример

s = abacaba$

[math] \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|l|l|l|l|l|} \hline \multicolumn{3}{|l|}{0 iteration} & \multicolumn{4}{l|}{1 iteration} & \multicolumn{4}{l|}{2 iteration} & \multicolumn{4}{l|}{3 iteration} \\ \hline p & & c & p & & & c & p & & & c & p & & & c \\ \hline 7 & \$ & 1 & 7 & \$a & $\langle1, 2\rangle$ & 1 & 7 & \$aba & $\langle1, 5\rangle$ & 1 & 7 & \$abacaba & $\langle1, 8\rangle$ & 1 \\ \hline 0 & a & 2 & 6 & a\$ & $\langle2, 1\rangle$ & 2 & 6 & a\$ab & $\langle2, 3\rangle$ & 2 & 6 & a\$abacab & $\langle2, 5\rangle$ & 2 \\ \hline 2 & a & 2 & 0 & ab & $\langle2, 3\rangle$ & 3 & 4 & aba\$ & $\langle3, 2\rangle$ & 3 & 4 & aba\$abac & $\langle3, 4\rangle$ & 3 \\ \hline 4 & a & 2 & 4 & ab & $\langle2, 3\rangle$ & 3 & 0 & abac & $\langle3, 4\rangle$ & 4 & 0 & abacaba\$ & $\langle4, 3\rangle$ & 4 \\ \hline 6 & a & 2 & 2 & ac & $\langle2, 4\rangle$ & 4 & 2 & acab & $\langle4, 3\rangle$ & 5 & 2 & acaba\$ab & $\langle5, 2\rangle$ & 5 \\ \hline 1 & b & 3 & 1 & ba & $\langle3, 1\rangle$ & 5 & 5 & ba\$a & $\langle5, 1\rangle$ & 6 & 5 & ba\$abaca & $\langle6, 7\rangle$ & 6 \\ \hline 5 & b & 3 & 5 & ba & $\langle3, 1\rangle$ & 5 & 1 & baca & $\langle5, 6\rangle$ & 7 & 1 & bacaba\$a & $\langle7, 6\rangle$ & 7 \\ \hline 3 & c & 4 & 3 & ca & $\langle4, 1\rangle$ & 6 & 3 & caba & $\langle6, 5\rangle$ & 8 & 3 & caba\$aba & $\langle8, 1\rangle$ & 8 \\ \hline \end{tabular} [/math]

Псевдокод

   /* преобразует масив count, так что
      теперь он содержит позиции в массиве suffs с которых 
      необходимо вставлять подстроки, начинающиеся с соответствующих символов */
   int[] calc_positions(count)
       count[0] = 0
       for i = 2 .. count.length
           count[i] += count[i - 1]
       return count
      
   /* принимает строку, для которой требуется построить суффиксный массив
      возвращает суффиксный массив */
   int[] suff_array(str)
       s += '$'
       ALPHABET = 255
       
       // нулевая итерация
       count = int[max(ALPHABET, 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
       curr_len = 1
       while cur_len [math] \leqslant [/math] str.length
           // сортируем по второй половине подстроки
           sorted_by2 = int[str.length]
           for i = 0 .. str.length
               sorted_by2[i] = (suffs[i] + str.length - cur_len) % str.length
           // сортируем по первой половине
           // сортировка устойчивая, значит получим целиком отсортированные подстроки
           fill(count, 0)
           for by2 in sorted_by2
               count[classes[by2]]++
           count = 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

См. также

Источники информации