Алгоритм Кнута, Морриса и Пратта

Рассмотрим сначала более простой случай. Пусть все символы строки-образца S различны. Начнем сравнивать символы S слева направо с первыми m символами строки A. Допустим, что несравнение символов произошло в позиции k £ m, т.е. первые k–1 букв A и S совпали. С какой позиции A следует начать новое сравнение с S? Поскольку символ ak–1 = sk–1, то ak–1 не может совпасть с предыдущими символами S (потому что все символы S различны). Значит, перед продолжением сравнения строк можно сдвинуть S так, чтобы ее первый символ сравнивался сразу с k-м символом A (т.е. с той позицией A, где было обнаружено несовпадение).

Если в S есть совпадающие символы, рассчитать величину сдвига несколько сложнее.

Определим dj как длину наибольшей подстроки из S, которая заканчивается в позиции j и совпадает с началом строки S во всех позициях, кроме последней. Это можно подробно расписать таким образом:

s[j – dj + 1] = s[1],

s[j – d j + 2] = s[2],

…, (7.1)

s[j – 1] = s[dj – 1],

но при этом s[j] ¹ s[dj]).

Если такой подстроки не существует, то положим dj = 0. Тогда нетрудно показать, что, если первое несовпадение при сравнении символов из A и S произошло на паре символов ai ¹ sj, то перед продолжением сравнения следует заменить индекс j на dj, а значение индекса i не изменять (т.е. надо сдвинуть строку S на j – dj позиций вдоль строки A). Действительно, поскольку символы a[i – dj + 1], a[i – d j + 2], …,
a[i – 1] успешно сравнились с символами s[j – dj + 1], s[j – d j + 2], …, s[j – 1], то они, согласно (7.1), должны сравниться и с символами s[1], s[2], …, s[dj – 1], а потому сравнение можно продолжать с пары символов a[i] и s[dj].

Если же значение j стало равно 0, то надо увеличить i и j на единицу, т.е. начать сопоставление символов заново с позиции i + 1 в строке A и с первой позиции в строке S.

Ниже приведены примеры значений dj, рассчитанных для различных строк-образцов.

1) a a a a a a 2) q w e r t y u i
  0 0 0 0 0 0   0 1 1 1 1 1 1 1
3) a a b a a b c 4) a b c d a c e f a b d f
  0 0 2 0 0 2 4   0 1 1 1 0 2 1 1 0 1 3 1
5) a b b a b b a c 6) a b a b a b a c a b c
  0 1 1 0 1 1 0 5   0 1 0 1 0 1 0 6 0 1 3

Рассмотрим работу алгоритма на примере, показанном на рис. 7.1. В строке A = ‘aabaabaaabaabc’ ищется подстрока S = ‘aabaabc’ (см. выше, пример 3).

  1 2 3 4 5 6 7 8 9 10 11 12 13 14
A: a a b a a b a a a b a a b c
Шаг 1: a a b a a b c              
Шаг 2:       a a b a a b c        
Шаг 3:               a a b a a b c

Рис. 7.1. Пример работы алгоритма Кнута, Морриса и Пратта

На первом шаге обнаруживается несовпадение букв ai и sj при i = 7 и j = 7. Выполняется присваивание j:= 4. Сравнение продолжается, пока при i = 9 и j = 6 не происходит очередное несовпадение. Делается присваивание j:= 2. На сей раз сравнение проходит успешно.

Отдельный вопрос – как лучше всего рассчитывать величины dj. В алгоритме на этот вопрос дается довольно неожиданный ответ. Задачу расчета dj для всех значений j можно рассматривать как модифицированную задачу поиска, в которой роль строки поиска играет S, а роль строки-образца – начальная часть той же строки. Поэтому вычисление dj выполняется примерно по тому же алгоритму, что и сам поиск вхождения S в A.

Ниже приведен текст функции, реализующей алгоритм поиска КМП.

function KMPSearch(A: StringN; S: StringM): Integer;

var

i, j, k: Integer;

d: Array[1..M] of Integer;

begin

{Вычисление d[j]}

j:= 1;

k:= 0;

d[1]:= 0;

while j < M do begin

while (k > 0) and (S[j] <> S[k]) do

k:= d[k];

j:= j + 1;

k:= k + 1;

if S[j] = S[k] then d[j]:= d[k]

else d[j]:= k;

end;

{Поиск}

i:= 1;

j:= 1;

while (j <= M) and (i <= N) do begin

while (j > 0) and (A[i] <> S[j]) do

j:= d[j];

i:= i + 1;

j:= j + 1;

end;

if j > M then

KMPSearch:= i – j + 1 {Успех}

else

KMPSearch:= 0; {Неудача}

end; {KMPSearch}

Можно доказать, что время работы алгоритма КМП Tмакс(n, m) = O(n+m). Это значительно лучше, чем оценка O(n×m) для прямого поиска, особенно для длинных строк-образцов.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: