
- •4. Задача поиска
- •4.1. Задача поиска на множестве
- •4.2. Задача динамического поиска
- •4.3. Порядковые статистики
- •4.4. Хеширование
- •4.5. Поиск подстрок
- •4.5.1. Алгоритм Рабина-Карпа
- •4.5.2. Алгоритм Кнута-Морриса-Пратта
- •4.5.3. Алгоритм Бойера-Мура
- •5. Сортировка, выпуклые оболочки
- •5.1. Задача сортировки
- •5.2. Задача построения выпуклой оболочки
- •5.2.1. Алгоритм Грэхема
- •5.2.2. Алгоритм Джарвиса
- •5.2.3. Метод разделяй и властвуй.
- •5.2.4. QuickHull
- •5.2.5. Алгоритм Чена
4.5.2. Алгоритм Кнута-Морриса-Пратта
Основной идеей метода, предложенного Кнутом, Моррисом и независимо Праттом, является идея предобработки с целью построения такого НКА, который, принимая на вход некоторый текст, мог бы распознавать все вхождения цепочек, определяемых шаблоном, в составе входной последовательности.
Рассмотрим задачу поиска всех вхождений
образца
в некоторый текст
,
.
Предположим, что в текущий момент времени
с образцом сравнивается подстрока
и при этом совпали первые
(
)
символов. При условии, что
,
мы можем говорить о неудачном завершении
поиска для подстроки
.
На следующем шаге алгоритм должен
сдвинуться по тексту, начав проверку
на совпадение образца со следующей
подстрокой. Переборный алгоритм продолжил
бы работу со сравнения символов
и
,
осуществив сдвиг по отношению к предыдущей
подстроке на один символ. Однако
оказывается, что величину сдвига можно
вычислять, используя информацию,
собранную при сравнении подстроки
.
В самом деле, при выполнении поиска мы
просмотрели множество
собственных суффиксов подстроки
.
При условии совпадения некоторого
суффикса длины
из указанного множества с префиксом
той же длины строки-образца нет
необходимости повторять сравнение
части образца
с подстрокой
,
а можно продолжить поиск сравнением
символов
и
.
Обозначим через
подмножество множества
,
содержащее подстроки, совпадающие с
некоторым префиксом строки-образца. Из
сказанного выше следует, что длины
строк, входящих в
и определяют соответствующие сдвиги
при поиске следующей подстроки в тексте,
причем величина сдвига равна разности
,
где
длина слова
.
Тогда минимальный сдвиг по тексту для
поиска следующего кандидата на совпадения
в строке определяется как
.
С другой стороны, поскольку каждый
элемент из
является некоторой подстрокой образца
вся информация, необходимая для построения
множества
содержится в образце. Другими словами
для каждого начального отрезка
длины
строки образца необходимо уметь находить
подстроку-префикс максимальной длины
,
которая совпадает с суффиксом
.
Дадим более строгое определение:
равно максимальному значению
,
,
для которого
.
Если такого
нет, то
=0.
Функция
носит название префикс-функции.
Префикс-функция несет информацию о том,
в каких местах строки-образца повторно
встречается префикс соответствующей
длины.
Пусть
является строкой образцом. Тогда,
несложно видеть, что
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
|
0 |
0 |
1 |
2 |
3 |
4 |
0 |
1 |
Рассмотрим задачу поиска образца в
тексте
.
Пусть алгоритм на некотором шаге проводит
сравнение с подстрокой
b |
b |
a |
b |
a |
b |
a |
b |
a |
a |
b |
a |
b |
a |
b |
b |
a |
|
|
a |
b |
a |
b |
a |
b |
b |
a |
|
|
|
|
|
|
|
Из рисунка видно, что поиск неудачно
завершается при сравнении седьмого
символа образца (подчеркнут). Также
непосредственно видно, что сравнивать
следующую подстроку с образцом не имеет
смысла, поскольку они имеют различные
первые символы. Необходимый сдвиг
определяется значением
.
После такого сдвига мы знаем, что первые
символов подстроки
совпадают с образцом и можем продолжить
поиск с 5 символа образца (заметим, что
в тексте это последний символ который
исследовался при предыдущем поиске),
до тех пор, пока не убедимся, что новое
расхождение имеет место на 6-м символе.
b |
b |
a |
b |
a |
b |
a |
b |
a |
a |
b |
a |
b |
a |
b |
b |
a |
|
|
|
|
a |
b |
a |
b |
a |
b |
b |
a |
|
|
|
|
|
Поскольку поиск вновь неудачно завершается
на 6-м символе образца, то очередной
сдвиг определяется как
.
Следовательно, при наличии префикс-функции
алгоритм на каждом шаге или сдвигает
указатель на одну позицию вправо по
тексту или сдвигает образец на число
позиций, определяемым значением
префикс-функции. Если текущий проверяемый
символ текста многократно не совпадает
с символом, следующим за текущим
префиксом, то следующий сдвиг вычисляется
итерацией префикс-функции, которая
определяется как
и
для
.
В самом деле, если предположить, что
поиск применялся к тексту
,
то после первого сдвига мы бы оказались
в ситуации, описываемой следующим
рисунком
b |
b |
a |
b |
a |
b |
a |
b |
с |
a |
b |
a |
b |
a |
b |
b |
a |
|
|
|
|
a |
b |
a |
b |
a |
b |
b |
a |
|
|
|
|
|
В этом случае указатель по тексту не
сдвинулся, а очередной сдвиг образца
равен
.
Изложим теперь алгоритм поиска в виде псевдокода, на вход которого подаются текст Т и образец Р в виде массивов в предположении, что мы умеем вычислять префикс-функцию
Procedure KMP_Matcher [4].
1.
2.
3.
//вычисляется
вектор значений префикс-функции по
образцу Р
4.
5.
6.
7.
8.
9.
10.
’образец
входит в текст с позиции’
11.
12
Как следует из текста приведенного кода
алгоритм при условии несовпадения
-го
символа текста с текущим
-м
символом префикса образца начинает
поиск другого (меньшего по длине префикса)
до тех пор, пока значение функции префикса
не станет равным нулю или произойдет
совпадение
-го
символа текста с
-м
символом префикса. Другими словами при
невозможности продвижения по тексту
далее алгоритм перебирает все варианты
совпадения префиксов пройденной части
текста с префиксами образца. Это
напоминает задачу слияния двух
упорядоченных массивов, при решении
которой асинхронно в одну сторону
двигаются указатели в соответствии со
значением предиката сравнения. В нашем
случае указатель на элементы меньшего
по длине массива (образца) может сдвигаться
на некоторое количество позиций влево,
с другой стороны при совпадении элементов
оба указателя одновременно сдвигаются
вправо.
Однако открытым остается вопрос вычисления префикс функции. Поскольку префикс-функция зависит только от образца, ее вычисление делается только один раз, но результаты этих вычислений могут быть многократно использованы при поиске в различных текстах.
Нетрудно видеть, что эта задача является
частным случаем задачи поиска в тексте
образца. Только в этом случае в качестве
текста берется образец для исходной
задачи, а в качестве образцов рассматриваются
различные префиксы этого образца.
Приведем алгоритм вычисления
префикс-функции
по образцу
[4]:
Procedure Compute_Prefix_function
1.
2.
3.
4.
5.
7.
8.
9.
10.
11
Внимательный читатель несомненно
заметит схожесть алгоритмов процедур
KMP_Matcherи
Compute_Prefix_function. В самом деле, зафиксировав
длину некоторого префикса образца, для
которого ведется поиск значения
префикс-функции, мы можем также, как и
в основной задаче, использовать
информацию, собранную на предыдущих
шагах алгоритма. В частности, если
совпадают два слова, то у них должны
совпадать и соответствующие префиксы
одинаковой длины. Поэтому в последней
процедуре заведены два указателяи
,
причем
указывает на текущий проверяемый символ
образца, а
определяет длину префикса образца,
который совпадает с
.
При совпадении следующих символов длина
возрастает, в противном случае длина
префикса уменьшается по правилу
.
Алгоритм поиска по образцу можно
представить в виде некоторого
недетерминированного автомата с одним
начальным и одним допускающим состоянием,
для которого разрешены
переходы.
Обратные переходы определяются значениями
префикс-функции и представляют переходы,
связанные с пустым символом
.
Начальным состоянием построенного
автомата является состояние 0,
заключительным состоянием состояние
8. Рассмотрим работу НКА на слове
.
Первые два такта автомат проводит в
состоянии
,
а затем под воздействием входной
подстроки
переходит в состояние
.
Поскольку из состояния 6 имеются
переходы, можно считать, что в этот
момент автомат одновременно находится
также в состояниях
.
Далее необходимо проанализировать
переходы автомата по очередному символу
.
Поскольку из состояния 6 такой переход
невозможен, анализируются переходы из
состояний 4, 2 и 0. В частности, если
рассматривать переход из состояния 4,
то по символу
можно попасть в состояния 5, 3, 1 и 0. Далее
на вход автомата вновь поступает символ
.
Поскольку для результативного поиска
нам необходимо рассмотреть хотя бы одну
траекторию, завершающуюся в заключительном
состоянии, то рассмотрим переход из
состояния 0. Несложно видеть, что завершая
ввод текста, мы переходим в распознающее
состояние 8. Таким образом, процедураKMP_Matcherявляется детерминированной реализацией
НКА, распознающего язык
,
где
представляет строку-образец. Множество
всех переходов НКА для данного входного
текста может быть представлено в виде
дерева переходов состояний. ПроцедураKMP_Matcherпредлагает способ перебора траекторий
в этом дереве, позволяющего получить
результат за линейное время.
Мы не будем делать детальный разбор
правильности приведенных псевдокодов.
Любознательного читателя можно отослать
к литературе [2,4], но заметим, что сложность
нахождения префикс-функции составляет
,
а сложность поиска по образцу в тексте
длины
равна
,
что лучше, чем в алгоритме Рабина-Карпа.
Можно также непосредственно построить
ДКА, распознающий язык
[2]. При построении этого автомата также
существенно используется префикс-функция.
Алгоритм построения такого автомата
по образцу
описывается следующим образом:
Положим
.
Функция переходов состояний может быть
построена следующей процедурой:
По образцу
строится префикс функция
.
Сложность такого алгоритма равна
,
где
число элементов множества
,
а общая сложность алгоритма поиска -
.
Поэтому конечный автомат строят в
случае, когда длина текста значительно
превосходит мощность алфавита образца.
Для нашего примера диаграмма переходов
состояний автомата выглядит следующим
образом: