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