Романовский Иосиф Владимирович, Дьяченко Василий Владимирович
ДЕМОНСТРАЦИОННЫЕ ПРОГРАММЫ: 2. СУФФИКСНЫЙ МАССИВ
Суффиксные массивы служат для решения многих задач со строками, например, задачи о подстроке [1]. Многие прикладные задачи (например, в генетике) требуют для своего решения многократный поиск в одной и той же строке. Выгодно провести предварительную обработку (препроцессинг) строки, то есть обработать её таким образом, чтобы потом поиск одного образца выполнялся как можно быстрее. Если образцов мало, это невыгодно и лучше пользоваться другими алгоритмами точного поиска в строках. Но чем больше образцов мы проверяем, тем препро-цессинг выгоднее использовать.
снещиалаАкй еом&м,,.
В 1993 году У. Манбер и Дж. Майерс [2] предложили для решения задачи о подстроке структуру, названную суффиксным массивом. В книге [1] описан метод построения этого массива с помощью суффик-сного дерева, который очень громоздок. В последнее время появилось много замечательных методов. Здесь описывается один из них [3, см. также 4]. О быстродействии алгоритмов можно судить по тому, что суффиксный массив для полного текста английского перевода Библии (4 мегабайта при 63 различных символах) строится ими за 1,52 секунды, а человеческая хромосома № 22 (32 мегабайта при 5 различных символах) -за 16 - 40 секунд (на компьютере с 512 MB памяти и процессором Интел 1,3 GHz под операционной системой Linux).
1. СУФФИКСНЫЙ МАССИВ
Пусть задана m-символьная строка T. Суффиксным массивом для T, обозначенным Pos, называется массив целых чисел от 1 до m, определяющих лексикографический порядок всех m суффиксов строки.
В конец строки для удобства поиска вставляется специальный символ, больше нигде в тексте не встречающийся. Он называется «сентинел»1 и обычно изобража-
1 Sentinel (англ.) — отдельно выставленный часовой, часто обозначающий границу охраняемой территории. В русском языке ему может соответствовать слово «флажковый». Именно флажковыми называются военнослужащие с флажками в начале и конце движущейся колонны.
fV>3| 1] 1 lAAlllllAAlIltetl.lÜAMlp
р+* Я 7 1,* |||,|,|ЬУ|||,|' l.lll.'.' i-fl
■7 1,импл.к1п,1.н1ппл>|
Р*4 л ^ttnadÉaUnatj
м S fTTT**rt~*Tlt)
PfleL н] * flwdthrtalaMl?
рЩ 7] 1 l.hlll.nl^l.l.ldil
5
F4)S| 9] [*lln*llltl
ГУ.1|1<|| 1G I>:l,iil7i,i.i'i i
Гч|Н|11| 11 1 1 Ti 11. Ti i- h
1Í ipnlrt+sVh
Р44ТО lcin«tl
к IÍHWS1
Гч,й|15| 15 laaít
рцщуц 14 («I
Р4Ш 17 il)
i ■ (И
PH| zj- n¡
ГтЦ - 1П ГллЗН
11 Нимф
Ptaf :. fizuchlniLidMinth
lk«l «1- i 1 i'^ÉI i и i'i i h > rl Ti i h'i n tj
7\- tí ЧкикЬнкикыаф
í >4 í|- 12 (нЬнаЦ
РЩ ■ 1« i.1ll.|i|.Uflll
2 ■ illlll-l-V^llliMl-lLklЛill ■
7 iiii|i|iii.t:lii,ir:-i
Ш412]- 15 lAvuifl
P.hf||3|- 4 ||1Л.|,||+1Л|1Л|1нЛЛ1-|
11 {МлтЪ
РМ41Ц - 9 NLnl.wlunfh
№(14 x J (Птт^МдДуТп!^
mnn* В 1 1 !.'■ i i i ^ ■
Рис. 1
Рис. 2
ется знаком «$». Этот символ лексикографически предшествует всем остальным.
Каждый суффикс строки определяется своим номером. Первый суффикс - это сама строка, последний суффикс - последняя буква строки. Для хранения суффиксов нам надо хранить только их номера. В массиве Pos хранятся номера суффиксов в таком порядке, что суффиксы с этими номерами расположены лексикографически. Так как в суф-фиксном массиве Pos хранятся только целые числа, а не строки, он не занимает много памяти.
В начале алгоритма суффиксный массив выглядит так (см. рис. 1).
В соответствии с поставленной задачей, для строки «aaddaaaddadadaaa$» суффиксный массив должен выглядеть следующим образом (см. рис. 2).
Использовать такой массив для поиска подстроки не сложно. Пусть надо найти вхождения строки «ad» в строку, суффикс-ный массив которой расположен выше. Для
этого достаточно найти номер первого суффикса начинающегося на Это несложно сделать двоичным поиском. Когда искомый номер найден (в данном случае это 8), все остальные суффиксы находятся рядом с ним в суффиксном массиве и имеют номера 9 10 11.
Таким образом, ответ: 12 10 2 7 - это и есть вхождения подстроки <^» в строку «aaddaaaddadadaaa$»
2. ПРИНЦИП ПОСТРОЕНИЯ СУФФИКСНОГО МАССИВА
Существует множество алгоритмов построения суффиксного массива, однако принцип работы у них почти одинаков. Определяются так называемые корзины - множества суффиксов, у которых первые несколько букв совпадают. Каждая корзина маркируется номером первого суффикса, входящего в упорядочение, и этот номер одинаков для всех суффиксов корзины.
Вот, например, как представляется в демонстрационной программе корзина суффиксов с первыми двумя одинаковыми буквами «аа»: Она имеет номер 3, так как перед этими суффиксами стоят суффиксы «$» и «a$» (рис. 3).
... —
у tcaeMjíUX Яеа&мыса йуе^ caifuzfoictñ.
s 4 a
g 4 а 0
Í| a л а о л d a il п П. a
й B. л а □ а л il и 41 ■П. ■э а
0 НО □ и а. •h а Л 41 |1 d л Л а л л S
Рис. 3
Суть алгоритмов в том, что суффиксы разбиваются по корзинам, а затем эти корзины уточняются (мы усиливаем условие нахождения двух суффиксов в одной корзине и таким образом разбиваем корзины на более мелкие). Процесс продолжается, пока в каждой корзине не останется только один суффикс.
3. АЛГОРИТМ
Теперь рассмотрим алгоритм построения массива, реализованный в демонстрационной программе.
Фаза первая
На первой фазе мы грубо раскидываем суффиксы по корзинам, обращая внимание только на первые две буквы суффиксов. (стоит отметить что здесь и далее по тексту операции совершаются не над суффиксами а над числами в суффиксном массиве, соответствующими этим суффиксам) Для этого достаточно сделать два сканирования по последовательности.
При первом сканировании мы вычисляем ключи сортировки для суффиксов. Клю-
Рис. 4
чи сортировки вычисляется из соображений лексикографической упорядоченности. Также при первом сканировании мы считаем статистику появления пар букв в строке.
После первого сканирования место под корзины распределяем согласно собранной статистике.
При втором сканировании просто пробегаемся по всем суффиксам и помещаем их по корзинам в соответствии с найденными ранее ключами сортировки.
Фаза вторая
После первой фазы мы имеем массив, в котором суффиксы лексикографически упорядочены по первым двум буквам. При этом, чтобы этот массив удовлетворял необходимому условию, нужно разобраться с суффиксами, для которых упорядочения по двум буквам не хватило. Такие суффиксы лежат с «конфликтующими» в одной корзине.
Вот пример работы демонстрационной программы для той же строки «ааёёаааёёаёаёааа$» после первой фазы (рис. 4).
Как видно из рисунка, необходимо уточнить корзину под номером 3 (которая приводилась в примере выше).
Уточнение корзины происходит следующим образом: сначала пробегаемся по суффиксам корзины и вычисляем ключи сортировки для суффиксов. Ключи сортировки на этот раз вычисляются из соображения что суффиксы уже отсортированы по первым двум буквам, а именно «на две буквы» мы и собираемся уточнить корзину. Например, для суффикса «ааёёаёаёааа$» ключ сортировки будет равен номеру корзины, в которой находится часть, по которой будет идти сравнение («ёёаёаёааа$»). В данном случае это 16 (см. рис. 5).
Для суффикса «aaaddadadaaa$» по аналогии, ключ его сортировки равен номеру корзины суффикса «addadadaaa$», то есть 8 (рис. 6).
После того как вычислены все ключи сортировки, просто сортируем суффиксы в корзине согласно этим ключам. На практике используется вставка (для корзин меньше 15-ти суффиксов) и Quicksort. Сразу после сортировки корзина разбивается на под-корзины меньшего размера.
В нашем примере, все же остается одна неразбитая корзина, которую мы уточним при следующем повторении второй фазы (рис. 7).
Аналогично разбиваем оставшиеся корзины. Здесь стоит отметить, что чем позднее стоит корзина, тем «лучше» будет в ней разбиение. Например, при уточнении 12-ой корзины, суффикс «daaa$» получит ключ сортировки отличный от «daaaddadadaaa$» благодаря тому, что корзина 3, ранее состоявшая из пяти суффиксов, была разбита. Таким образом, мы используем результаты разбиения на корзины сразу же после этого разбиения.
После прохода по всем корзинам, имеем массив суффиксов, упорядоченных по первым четырем буквам. Если этого недостаточно (есть хотя бы одна корзина с двумя суффиксами) - повторяем вторую фазу, но уже пользуясь тем что суффиксы упорядочены по большему числу букв (4). Если и после этого пробега останутся неразбитые корзины, снова повторяем вторую фазу с условием упорядоченности по первым восьми буквам. И так далее по степеням двойки до тех пор пока не получим по одному суффиксу в одной корзине.
...после оорнирОки мор^иНл рл^ёа&леЛс^ На шофкар^ссЯи меЯмиеио размера.
То, что получится после многократного выполнения второй фазы, и будет суффик-сным массивом.
; ■ М В а & з л ¡1
1 а в в
а о И , il L-J * 1 * | Q
■ iL Щ 4L А а А d А ■ A
а J о □ .1 а л J J Л J A >1 A A A i
г ш щ щ
9 d Н а о £1 1 А 41 [& ■ ч •я \ Т|
П 'L п А d Л 4 Л \ д j a. ■i ■ J_ _
Рис. 5
w 1 ■a т —1 п
и jjj ■ □
Щ 14 а в Л А 4] a a а. 1 5
* A ¿п I Е А a * Т |
Щ л EJ □ J А d г j а | ■ > .1 и S
g il ш Я 5
9 П1 о л Л
If il i — ■ 3 В _1 A J 2
k □ а * « (1 * . Е 1 4J ■ L .1 а i
_ _ 1 —■—м
Рис. 6
Рис. 7
4. ИНТЕРФЕЙС
Для запуска демонстрации необходимо нажать кнопку «воспроизведения» в нижней правой части экрана. Для лучшего по-
нимания алгоритма рекомендуется нажать на кнопку «пауза» в правом нижнем углу и смотреть демонстрацию по шагам, читая разъясняющие комментарии для каждого шага (рис. 8).
Рис. 8
Литература
1. Гасфилд Дэн. Строки, деревья и последовательности в алгоритмах. СПб.: «Невский диалект», БХВ-Петербург, 2003. 654 с.
2. Manber U., Myers G. Suffix arrays, pages a new method for on-line search // SIAM J. Comput., 22, 1993. P. 935-948.
3. Schurmann K.-B., Stoye J. An incomplex algorithm for fast suffix array construction // Software, Pract. Exper. 37(3), 2007. P. 309-329.
4. Bentley J.L., Mellroy M.D. Engineering a sort function // Software, Pract.Exper. 22(11), 1993. P. 1249-1265.
@ Наши авторы. 2007 Our authors. 2007
Романовский Иосиф Владимирович, доктор физико-математических наук, профессор СПбГУ,
Дьяченко Василий Владимирович, студент математико-механического факультета СПбГУ.