Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекция2.doc
Скачиваний:
4
Добавлен:
10.12.2018
Размер:
214.53 Кб
Скачать

1.5. Поиск в строке. Алгоритм Кнута, Мориса и Пратта

Приблизительно в 1970 г. Д. Кнут, Д. Морис и В. Пратт изобрели алгоритм, фактически требующий только N сравнений даже в самом плохом случае. Новый алгоритм основывается на том сообра­жении, что, начиная каждый раз сравнение образа с самого начала, мы можем уничтожать ценную ин­формацию. После частичного совпадения начальной части образа с соответствующими символами строки мы фактически знаем пройденную часть строки и можем «вычислить» некоторые сведения (на основе самого образа), с помощью которых потом быстро продвинемся по тексту. Приведенный пример поиска слова «иваны» показывает принцип работы такого алгоритма. Символы, подвергшиеся сравнению, здесь подчеркнуты. Обратите внимание: при первом несов­падении двух символов образ сдвигается на все прой­денное расстояние, поскольку меньшие сдвиги не мо­гут привести к полному совпадению

КМП-алгоритм записывается так (опять используется предикат Р (1.47) и Q (1.48))

i:=1; j:=1; (1.50)

WHILE ( j <= M ) AND ( i <= N) DO BEGIN

(* Q( i-j ) & P( i-j, j ) *)

WHILE ( j >= 1 ) AND ( s[ i ] <> p[ j ] ) DO j:=D;

i:=i+1; j:=j+1;

END;

Однако такая запись умышленно не совсем точная, поскольку в ней есть сдвиг на неопределенную вели­чину D. Вскоре мы к ней вернемся, а теперь сначала отметим, что условия Q( i-j ) и P( i-j, j ) сохра­няются как глобальные инварианты и к ним можно добавить отношения 1 ≤ iN и 1 ≤ jM. Это предполагает, что мы должны отказаться от соглашения, что i всегда отмечает в тексте текущее поло­жение первого символа образа. Точнее, выравненное положение образа теперь i - j.

Если алгоритм заканчивает работу по причине j=М+1, то из составляющей P( i-j, j ) следует спра­ведливость P( i-М, М ), т. е., согласно (1.47), сов­падение начинается с позиции i - М. Если же работа окончена из-за i = N+1, то поскольку j < М+1, из инва­рианта Q( i ) следует, что совпадения вообще нет.

Теперь мы должны показать, что алгоритм ни­когда не делает инвариант ложным. Легко видеть, что в начале i=j=1, и он истинен. Сначала иссле­дуем эффект от двух операторов, увеличивающих i и j на единицу. Они, очевидно, не сдвигают образ вправо и не делают ложным Q( i – j ), поскольку раз­ность остается неизменной. Но, может быть, они де­лают ложным P( i—j, j ) - вторую составляющую ин­варианта? Обращаем внимание, что в этой точке истинно отрицание выражения в заголовке внутрен­него цикла, т. е. либо j < 1, либо si=pj. Последнее увеличивает частичное совпадение и устанавливает P( i-j, j+1 ), а первое так же постулирует истин­ность P( i-j, j+1 ). Следовательно, увеличение на единицу i и j не может также сделать ложным тот или другой инвариант. Но в алгоритме остается толь­ко еще присваивание j:=D. Мы просто постулируем, что значение D всегда таково, что замена j на D оставляет инвариант справедливым.

Для того чтобы найти соответствующее выраже­ние для D, мы должны вначале понять смысл этого присваивания. При условии что D < j, присваивание соответствует сдвигу образа вправо на j - D позиций. Естественно, мы хотим, чтобы сдвиг был настолько больше, насколько это возможно, т. е. D должно быть как можно меньше. Этот процесс показан на рис. 1.1.

i

a

b

c

d

a

b

c

e

a

b

c

d

a

b

c

e


j=4 D=1 j=1

Рис. 1.1. Присваивание j:=D сдвигает образ на j—D по­зиции вправо (в данном случае D=1, до присваивания j=4).

Если инвариант P( i-j, j ) & Q( i-j ) после при­сваивания j значения D истинен, то перед ним долж­но быть истинно P( i-D, D ) & Q( i-D ). Это предусло­вие и будет поэтому нашим ориентиром при поиске под­ходящего выражения для D. Основное соображение:

благодаря P( ij, j ) мы знаем, что

si- j. … si-1 = p1 … pj-1

(мы только что просмотрели первые j символов образа и убедились в их совпадении с текстом). Поэтому условие P( i-D, D ) с D ≤ j, т. е.

si-D. … si-1 = p1 … pD-1

превращается в

pj-D. … pj-1 = p1 … pD-1 (1.51)

и предикат ~P(i—k, М) для k=1...j-D (чтобы убедиться в инвариантности Q(i-D)) превращается в

pj-k. … pj-1 ≠ p1 … pk-1 (1.52)

Важный вывод: очевидно, что значение D опреде­ляется одним лишь образом и не зависит от строки текста. Условия (1.51) и (1.52) говорят, что для определения D мы должны для каждого j найти наименьшее D, т. е. самую длинную последователь­ность символов образа, непосредственно предшест­вующих позиции j, которая совпадает полностью с началом образа. Для каждого j такое D мы будем обозначать через dj. Так как эти значения зависят только от образа, то перед началом фактического поиска можно вычислить вспомогательную таблицу d; эти вычисления сводятся к некоторой предтрансляции образа. Соответствующие усилия будут оправ­данными, если размер текста значительно превышает размер образа (М<<N). Если нужно искать многие вхождения одного и того же образа, то можно пользоваться одними и теми же D. Приведенные примеры объясняют функцию D.

Примеры:

a

a

a

a

a

a

j=6; d[6]=4+1; (макс.сдвиг=j-d[j]=1)

p[1]…p[4]=p[2]…p[5]

a

a

a

a

a

b

a

a

a

a

a

b

a

b

c

a

b

c

j=6; d[6]=2+1; (макс.сдвиг=j-d[j]=3)

p[1]…p[2]=p[4]…p[5]

p[1]…p[3]≠p[3]…p[5]

p[1]…p[4]≠p[2]…p[5]

a

b

c

a

b

d

a

b

c

a

b

c

a

b

c

d

e

a

j=6; d[6]=0+1; (макс.сдвиг=j-d[j]=5)

p[1]…p[1]≠p[5]…p[5]

p[1]…p[2]≠p[4]…p[5]

p[1]…p[3]≠p[3]…p[5]

p[1]…p[4]≠p[2]…p[5]

a

b

c

d

e

f

a

b

c

d

e

f

Рис.1.2. Вычисление dj

Последний пример на рис.1.2. и примеры на рис.1.3. (два его частных случая) наводят на мысль, что мы можем поступать еще лучше: так как рj рав­но А вместо F (рис.1.3.), то соответствующий символ строки не может быть символом А из-за того, что условие si≠рj заканчивает цикл. Следовательно, сдвиг на 5 не приведет к последующему совпадению, и поэтому мы можем увеличить размер сдвига до шести (см. рис. 1.3., верхний пример). Учитывая это, мы пере­определим вычисление dj как поиск самой длинной совпадающей последовательности

p1. … pdj-1 = pj-dj … pj-1

с дополнительным ограничением pdj ≠ pj. Если ника­ких совпадений нет, то мы считаем dj=0, что указывает на сдвиг «на целый» образ относительно его текущей позиции (см. рис. 1.3., нижняя часть).

a

b

c

d

e

f

a

b

c

d

e

a

a

b

c

d

e

j=6, d[6]=0; сдвиг=j-d[j]=6

a

b

c

d

e

f

a

b

c

d

e

g

a

b

c

d

e

f

j=6, d[6]=0; сдвиг=j-d[j]=6

Рис. 1.3. Сдвиг образа за последний символ

Ясно, что вычисление dj само представляет собой первое приложение поиска в строке, и мы для этого можем использовать быструю версию КМП-алгоритма. Это и демонстрирует программа 1.2, состоящая из двух частей. В первой читается строка s, а затем идут итерации, начинающиеся с чтения образа, затем следуют его предтрансляция и, наконец, сам поиск.

CONST Mmax = 100; Nmax = 1000; ESC = ЗЗС;

VAR

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]