Алгоритм КМП похож на алг-м распознавания с помощью КА, но позволяет избавиться от главного недостатка последнего – высокой трудоемкости построения таблицы переходов и затрат памяти на ее сохранение.
В алгоритме КМП строится не КА, а похожая на него машина распознавания, которая может делать несколько последовательных переходов из текущего состояния при анализе очередного символа текста.
Рассмотрим пример распознавания подстроки (выделены совпадающие символы текста и , текущее состояние , анализируемый символ – красный):
, но , поэтому машина не может перейти в , а должна перейти в новое состояние (т.е. сдвинется вправо относительно ). Используя уже проверенную , перейдем в наибольшее возможное состояние , для к-го , и сравним и :
, повторяем попытку для макс-ного , :
: переходим в сост-е , символ проверен.
Отметим, что текущий анализируемый символ никак не влияет на выбор следующего состояния машины распознавания: в состоянии (для к-рого ) выбирается максимальное состояние , для к-рого .
При этом , т.е. является максимальным по длине префиксом , который совпадает с суффиксом . Отсюда следует, что очередное состояние определяется только подстрокой и никак не зависит от проверяемого текста .
Для поиска нужного состояния при распознавании подстроки-образца заранее вычисляется целочисленная префикс-функция (функция отказов):
, .
Таблица значений для подстроки :
Машина распознавания подстроки :
Последовательность состояний при анализе строк:
aabaaa … 1-2-3-4-5-2-1-2...
aabaab … 1-2-3-4-5-6…
aabaac … 1-2-3-4-5-2-1-0…
При анализе очередного символа возможен возврат через несколько состояний.
Если текущее состояние , то можно сделать переходов в предыдущие состояния.
Но если машина распознавания находится в состоянии , должно выполняться условие .
=>
При анализе символов было сделано ровно переходов в последующие состояния (по одному переходу на каждый символ, без переходов в предыдущие).
=>
На каждый проверяемый символ текста приходится в среднем переходов.
Вычисление префикс-функции для подстроки :
F[1] = 0;
for (q = 2; q <= m; q++) {
k = F[q–1];
while (k > 0 && P[q]!= P[k+1]) k = F[k];
F[q] = (P[q] == P[k+1])? k+1: 0;
}
В цикле while можно сделать максимум шаг, но это возможно, если до этого был выполнен шаг цикла for, на которых цикл while вообще не выполнялся.
=>
Общая трудоемкость данного алгоритма составляет .
Поиск всех вхождений подстроки в строку после вычисления префикс-функции :
for (q = 0, i = 1; i <= n; i++) {
while (q > 0 && P[q+1]!= T[i]) q = F[q];
if (P[q+1] == T[i]) q++;
if (q == m) {
найдено_вхождение(i–m); q = F[q];
}
}
Рассуждая по аналогии с алгоритмом вычисления , можно сделать вывод, что трудоемкость данного алгоритма .
=>
Общая трудоемкость алгоритма КМП составляет и не зависит от количества символов в алфавите.
Вариант алгоритма Бойера-Мура с вычислением функций стоп-символа и безопасного суффикса (Кормен).
Алгоритм Рабина-Карпа
При поиске подстроки в строке необходимо найти все допустимые сдвиги, т.е. такие значения , , для которых .
Основная идея алгоритма:
- преобразовать подстроку и сравниваемую с ней часть текста в целые числа, заданные цифрами в системе счисления с основанием (обозначим эти числа, соответственно, и );
- сравнивать не и , а и (одно сравнение чисел вместо сравнений символов);
- обеспечить быстрое вычисление числа , соответствущего сдвигу , преобразованием значения .
Пусть . Тогда:
Сдвиг является допустимым, если .
Для вычисления не требуется знание строки . Используя схему Горнера, можно получить за время :
Также за можно вычислить :
Значения и связаны рекуррентным соотношением:
Если заранее получить константу (за время ), то будет вычисляться по известному за ЭШ, а проверку всего текста можно провести за .
К сожалению, числа и могут быть очень большими. Если , то уже при мы выйдем за пределы представления 4-байтовых целых чисел. Поэтому отдельные арифметические операции, на основании которых получены оценки трудоемкости, нельзя считать элементарными шагами.
Для преодоления этой трудности нужно перейти к модульной арифметике: вместо сверхдлинных целых чисел использовать их остатки от деления по некоторому модулю .
Если задано некоторое целое , то любое произвольное целое можно единственным образом представить в виде:
, где ( - частное, - остаток).
Для любых целых выполняется равенство:
,
т.е. операцию деления по модулю можно вносить в скобки и применять к отдельным значениям.
Чтобы исключить переполнение для целых в алгоритме Рабина-Карпа необходимо выбрать такое (простое), чтобы значение помещалось в машинное слово, и при расчете , , и постоянно вычислять остатки по :
вместо использовать ; при вычислении брать остаток после каждого умножения текущего числа на ,
,
,
.
Приведенные выше оценки трудоемкостей сохранятся, но теперь все вычисления проводятся с короткими числами, и каждая арифметическая операция будет одним ЭШ.
Пример вычисления для строки из десятичных цифр (, , Кормен и др.):
14152 = (31415 - 3∙10000) ∙10 + 2 – для длинных целых.
14152 mod 13 =
= ((31415 mod 13 – (3∙10000) mod 13) ∙10 + 2) mod 13 =
= ((7 – 9) ∙10 + 2) mod 13 = 8 – для остатков.
Правильное вычисление r = a mod q (при q > 0):
r = a % q;
if (r < 0) r += q;
Если , то и . Но из еще не следует . Поэтому при совпадении остатков необходимо проверить, найдено ли действительно вхождение подстроки (т.е. выполняется ли ) или произошло холостое срабатывание.
В наихудшем случае (например, если и являются последовательностями из одного символа алфавита) будет постоянно выполняться и последующее посимвольное сравнение. Поэтому трудоемкость в наихудшем составляет , как в простейшем алгоритме.
Но в большинстве практических приложений совпадений остатков и холостых срабатываний должно быть немного, и трудоемкость алгоритма Рабина-Карпа составит .
Поиск всех вхождений в ():
p = P[1]; t = T[1]; h = 1;
for (i = 2; i <= m; i++) {
h = mod(h*d, q);
p = mod(p*d + P[i], q);
t = mod(t*d + T[i], q);
}
for (s = 0; s <= n-m; s++) {
if (p == t) {
for (i = 1; i <= m && P[i] == T[s+i]; i++);
if (i > m) найдено_вхождение(s);
}
if (s < n-m)
t = mod(mod(t–T[s+1]*h, q)*d+T[s+m+1], q);
}
Возможность использования приведенных алгоритмов для распознавания набора подстрок.