Алгоритм Кнута-Морриса-Пратта (КМП) получает на вход
слово
и просматривает его слева направо буква за буквой, заполняя при
этом массив натуральных чисел
, где
(функция 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}`