Алгоритм Крочемора — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Реализация)
(Реализация)
Строка 113: Строка 113:
 
= Реализация =
 
= Реализация =
 
Классифицируем основные структуры данных в соответствии с их основными функциями:
 
Классифицируем основные структуры данных в соответствии с их основными функциями:
* Массив <tex>seq</tex> {{---}} <tex>seq[i]</tex> содержит индекс текущей последовательности, которой принадлежит <tex>i-я</tex> позиция
+
* Массив '''seq''' {{---}} <tex>seq[i]</tex> содержит индекс текущей последовательности, которой принадлежит <tex>i-я</tex> позиция
* Массив <tex>seqlist</tex> {{---}} <tex>seqlist[i]</tex> содержит указатель на двусвязный список позиций, принадлежащих последовательности с индексом <tex>j</tex> и расположенных в порядке их возрастания
+
* Массив '''seq_list''' {{---}} <tex>seq_list[i]</tex> содержит указатель на двусвязный список позиций, принадлежащих последовательности с индексом <tex>j</tex> и расположенных в порядке их возрастания
* Массив <tex>seqsize</tex> {{---}} <tex>seqsize[i]</tex> равно количеству позиций в последовательности с индексом <tex>j</tex>, т.е. количеству последовательностей в списке, на который указывает <tex>seqlist[j]</tex>
+
* Массив '''seq_size''' {{---}} <tex>seq_size[i]</tex> равно количеству позиций в последовательности с индексом <tex>j</tex>, т.е. количеству последовательностей в списке, на который указывает <tex>seq_list[j]</tex>
* Стек indexstack {{---}} стек неиспользованных индексов последовательностей
+
* Стек '''index_stack''' {{---}} стек неиспользованных индексов последовательностей
  
 
= Источники =
 
= Источники =

Версия 15:42, 12 июня 2014

Определение:
Тандемным повтором (англ. "tandem repeat") в строке называются два вхождения какой-либо подстроки подряд. Иными словами, тандемный повтор описывается парой индексов [math]i \lt j[/math] такими, что подстрока [math]s[i \ldots j][/math] — это две одинаковые строки, записанные подряд


Алгоритм Крочемора (англ. Crochemore algorithm) — алгоритм на строках, позволяющий найти все тандемные повторы в строке [math]s[1..n][/math] за [math]O(n \log n)[/math]

Алгоритм

Разобьем описание алгоритма на две части: сначала покажем упрощенный алгоритм, работающий за [math]O(n^2)[/math], а затем попытаемся его оптимизировать до [math]O(n \log n)[/math]

Упрощенный алгоритм

Рассмотрим следующую строку Фиббоначи:

1 2 3 4 5 6 7 8 9 10 11 12 13
[math]f_6 = [/math] a b a a b a b a a b a a b


Будем вычислять все повторяющиеся подстроки длины [math]l[/math], где [math]l = 1 \ldots n - 1[/math]. Зная эти данные, мы автоматически находим все тандемные повторы.

Предположим, что в строке [math]f_6[/math] вычислены последовательности позиций, в которых встречаются одинаковые символы:

[math]l = 1[/math] <1, 3, 4, 6, 8, 9, 11, 12> <2, 5, 7, 10, 13>
a b

Если нам заранее известен алфавит и он индексирован, то мы можем выполнить данное вычисление за [math]O(n)[/math].

Далее для [math]l = 2[/math] мы хотим найти все повторяющиеся подстроки длины [math]2[/math]. Поскольку повторяющиеся подстроки длины [math]l \geqslant 2[/math] будут иметь общий префикс длиной [math]l - 1[/math], то вычисления уровня [math]l[/math] должны привести к последовательностям, которые будут подпоследовательностями последовательностей, вычисленных на уровне [math]l - 1[/math]. Другими словами, разбиение на уровне [math]l \geqslant 2[/math] — декомпозиция разбиения на уровне [math]l - 1[/math]:

Последовательная декомпозиция строки [math]f_6 = abaababaabaab[/math]
[math]l = 2[/math] <1, 4, 6, 9, 12> <3, 8, 11> <2, 5, 7, 10> <13>
ab aa ba b$
[math]l = 3[/math] <1, 4, 6, 9> <12> <3, 8, 11> <2, 7, 10> <5>
aba aa$ aab baa bab
[math]l = 4[/math] <1, 6, 9> <4> <3, 8> <11> <2, 7, 10>
abaa abab aaba aab$ baab
[math]l = 5[/math] <1, 6, 9> <3> <8> <2, 7> <10>
abaab aabab aabaa baaba baab$
[math]l = 6[/math] <1, 6> <9> <2> <7>
abaaba abaab$ baabab baabaa
[math]l = 7[/math] <1> <6>
abaabab abaabaa

Если реализовывать процесс декомпозиции "наивно", то поучаем сложность [math]O(n^2)[/math]

Оптимизация

Декомпозицию каждой последовательности можно получить косвенным путем, а не путем прямых вычислений. Идея такого подхода состоит в следующем: на каждом уровне [math]l[/math] выполняется непосредственная декомпозиция каждой последовательности [math]c^{l}_j[/math]. Более точно, если [math]c^{l}_j = \langle p_1, p_2, \ldots , p_r \rangle[/math], то необходимо проверить совпадение букв [math]s[p_1 + l], s[p_2 + l], \ldots, s[p_r + l][/math], и, если какие-либо пары букв [math]s[p_i + l][/math] и [math]s[p_j + l][/math] равны, то [math]p_i[/math] и [math]p_j[/math] помещаются в одну и ту же последовательность на уровне [math]l + 1[/math].


Заметим, что декомпозицию можно выполнить, основываясь не на разбиваемой последовательности, а на последовательностях, относительно которых будут разбиваться другие последовательности.


Для каждой позиции [math]p_i \gt 1[/math] известно, что подстрока [math]s[p_i - 1 \ldots p_i + l - 1][/math] (длиной [math]l + 1[/math]) относится к некоторой последовательности [math]c^{l + 1}_{j'}[/math] на уровне [math]l + 1[/math]. Поскольку последовательность [math]c^{l}_{j}[/math] соответствует уникальной подстроке строки [math]s[/math], то каждая такая последовательность [math]c^{l + 1}_{j'}[/math] должна формироваться из тех же позиций [math]p_{i_1}, p_{i_2}, \ldots , p_{i_k}[/math] последовательности [math]c^{l}_{j}[/math], которые определяют класс эквивалентности [math]s[p_{i_1} - 1] = s[p_{i_2} - 1] = \ldots = s[p_{i_k} - 1][/math].


Таким образом, декомпозицию на уровне [math]l + 1[/math] можно выполнить косвенным путем, рассматривая каждую последовательность уровня [math]l[/math] с позиции, находящейся на [math]1[/math] левее от начальной позиции этой последовательности.


Лемма:
В каждом наборе последовательностей, порожденных одной последовательностью уровня [math]l - 1[/math], всегда можно исключить использование одной из них для декомпозиции последовательностей на уровне [math]l[/math]
Доказательство:
[math]\triangleright[/math]
TBA
[math]\triangleleft[/math]


Определение:
В декомпозиции последовательности [math]c^l_j[/math] на последовательности [math](c^{l+1}_1, c^{l+1}_2, \ldots, c^{l+1}_q), q \geqslant 1 [/math] назовем одну последовательность с наибольшим количеством элементов большой, а остальные [math]q - 1[/math] последовательности - малыми. Для [math]l = 1[/math] все последовательности будем считать малыми.


Лемма:
Предположим, что декомпозиция последовательностей, соответствующих произвольной строке [math]s[1..n][/math], выполняется для уровней [math]l = 1, 2, \ldots, l^*[/math], где [math]l^*[/math] — наименьший уровень, на котором каждая последовательность содержит единственную позицию. Тогда каждая позиция [math]i[/math] строки [math]s[/math] входит в малые последовательности [math]O(\log n)[/math] раз
Доказательство:
[math]\triangleright[/math]
Заметим, что если последовательность [math]c^l_j[/math] разбивается на подпоследовательности [math](c^{l+1}_1, c^{l+1}_2, \ldots, c^{l+1}_q)[/math], то каждая малая последовательность [math]c^{l+1}_{j'}[/math] удовлетворяет условию [math]c^{l+1}_{j'} \leqslant {c^{l}_{j} \over 2}[/math]. Другими словами, при [math]l geqslant 2[/math] каждая малая последовательность не превышает половины размера своей исходной последовательности. Поскольку для [math]l - 1[/math] начальная малая последовательность может содержать не более n позиций, то из этого следует, что ни одна из позиций не может входить в больше, чем [math]\log_2(n+1)[/math] малых последовательностей.
[math]\triangleleft[/math]


Поскольку строка [math]s[/math] содержит [math]n[/math] позиций, то из предыдущей леммы следует, что всего в малых последовательностях на всех уровнях содержится [math]O(n \log n)[/math] позиций. Таким образом, если время обработки последовательностей на каждом уровне [math]l[/math] пропорционально количеству элементов в малых последовательностях этого уровня, то полный процесс декомпозиции будет выполнен за [math]O(n \log n)[/math], чего мы и хотели получить.

Псевдокод

  crochemore()
     [math]l[/math] [math]\gets[/math] 1
     Вычислим все последовательности на уровне 1 и пометим их как малые
     while [math]\exists[/math] малая последовательность на уровне [math]l[/math]:
        out [math]\gets[/math] кратные строки с периодом l
        Вычислим декомпозицию последовательностей уровня [math]l[/math], используя только малые последовательности
        l++
        Найдем малые последовательности на уровне [math]l[/math]

Реализация

Классифицируем основные структуры данных в соответствии с их основными функциями:

  • Массив seq[math]seq[i][/math] содержит индекс текущей последовательности, которой принадлежит [math]i-я[/math] позиция
  • Массив seq_list[math]seq_list[i][/math] содержит указатель на двусвязный список позиций, принадлежащих последовательности с индексом [math]j[/math] и расположенных в порядке их возрастания
  • Массив seq_size[math]seq_size[i][/math] равно количеству позиций в последовательности с индексом [math]j[/math], т.е. количеству последовательностей в списке, на который указывает [math]seq_list[j][/math]
  • Стек index_stack — стек неиспользованных индексов последовательностей

Источники