Алгоритм Кнута-Морриса-Пратта (КМП) получает на вход
слово
и просматривает его слева направо буква за буквой, заполняя при
этом массив натуральных чисел
, где
(функция l определена в предыдущем пункте). Словами: l[i]
есть длина наибольшего начала слова
,одновременно являющегося его концом.
10.4.1. Какое отношение все это имеет к поиску подслова? Другими
словами, как использовать алгоритм КМП для определения того,
является ли слово A подсловом слова B?
10.4.2. Описать алгоритм заполнения таблицы
.

Другими словами, нас интересуют начала Z слова
одновременно являющиеся его концами
-- из них нам надо выбрать самое длинное. Откуда берутся эти
начала? Каждое из них (не считая пустого)
получается из некоторого слова Z'
приписыванием буквы x[i+1]. Слово Z' является началом и
концом слова
. Однако не любое слово,
являющееся началом и концом слова
,годится -- надо, чтобы за ним следовала буква x[i+1].
Получаем такой рецепт отыскания слова Z . Рассмотрим все
начала слова
, являющиеся одновременно
его концами. Из них выберем подходящие -- те, за которыми идет
буква
. Из подходящих выберем самое длинное.
Приписав в его конец x[i+1], получим искомое слово Z .
Теперь пора воспользоваться сделанными нами приготовлениями и вспомнить, что все слова, являющиеся одновременно началами и концами данного слова, можно получить повторными применениями к нему функции l из предыдущего раздела. Вот что получается:
i:=1; l[1]:= 0;
{таблица l[1]..l[i] заполнена правильно}
while i <> n do begin
| len := l[i]
| {len - длина начала слова x[1]..x[i], которое является
| его концом; все более длинные начала оказались
| неподходящими}
| while (x[len+1] <> x[i+1]) and (len > 0) do begin
| | {начало не подходит, применяем к нему функцию l}
| | len := l[len];
| end;
| {нашли подходящее или убедились в отсутствии}
| if x[len+1] = x[i+1] do begin
| | {x[1]..x[len] - самое длинное подходящее начало}
| | l[i+1] := len+1;
| end else begin
| | {подходящих нет}
| | l[i+1] := 0;
| end;
| i := i+1;
end;`
10.4.3. Доказать, что число действий в приведенном только что
алгоритме не превосходит
для некоторой константы C .
Более точно, можно записать неравенство
или
Остается сложить эти неравенства по всем i и получить
оценку сверху для общего числа итераций.![]()
10.4.4. Будем использовать этот алгоритм, чтобы выяснить, является
ли слово X длины n подсловом слова Y длины m.
(Как это делать с помощью специального разделителя #,
описано выше.) При этом число действий будет не более
, и используемая память тоже. Придумать, как
обойтись памятью не более
(что может быть существенно
меньше, если искомый образец короткий, а слово, в котором его
ищут -- длинное).
На практике слова X и Y могут не находиться подряд,
поэтому просмотр слова X и затем слова Y удобно оформить
в виде разных циклов. Это избавляет также от хлопот с
разделителем.![]()
10.4.5. Написать соответствующий алгоритм (проверяющий, является ли
слово
подсловом слова
).
j:=0; len:=0;
{len - длина максимального начала слова X, одновременно
являющегося концом слова y[1]..j[j]}
while (len <> n) and (j <> m) do begin
| while (x[len+1] <> y[j+1]) and (len > 0) do begin
| | {начало не подходит, применяем к нему функцию l}
| | len := l[len];
| end;
| {нашли подходящее или убедились в отсутствии}
| if x[len+1] = y[j+1] do begin
| | {x[1]..x[len] - самое длинное подходящее начало}
| | len := len+1;
| end else begin
| | {подходящих нет}
| | len := 0;
| end;
| j := j+1;
end;
{если len=n, слово X встретилось; иначе мы дошли до конца
слова Y, так и не встретив X}`