- •Основные алгоритмы
- •1.1. Линейный поиск
- •Весь массив просмотрен и совпадения не обнаружено.
- •1.2. Поиск делением пополам (двоичный поиск)
- •If found then writeln( ‘искомый элемент ‘,a [ m ] );
- •1.3. Поиск в таблице
- •X: String;
- •1.4. Прямой поиск строки
- •1.5. Поиск в строке. Алгоритм Кнута, Мориса и Пратта
- •I, j, k, k0, m, n: integer;
- •1.6. Поиск в строке. Алгоритм Боуера и Мура
1.4. Прямой поиск строки
Часто приходится сталкиваться со специфическим поиском, так называемым поиском строки. Его можно определить следующим образом. Пусть задан массив s из N элементов и массив р из М элементов, причем 0 < М ≤ N. Описаны они так:
s: ARRAY[ 1 .. N ] OF item
p: ARRAY[ 1 .. М ] OF item
Поиск строки обнаруживает первое вхождение р в s. Обычно item—это символы, т. е. s можно считать некоторым текстом, а р - образом или словом, и мы хотим найти первое вхождение этого слова в указанном тексте. Это действие типично для любых систем обработки текстов, отсюда и очевидная заинтересованность в поиске эффективного алгоритма для этой задачи. Однако, прежде чем обращать внимание на эффективность, давайте сначала разберем некий «прямолинейный» алгоритм поиска. Мы его будем называть прямым поиском строки.
Еще до определения алгоритма нужно более точно сформулировать, что же мы от него желаем получить. Будем считать результатом индекс i, указывающий на первое с начала строки совпадение с образом. С этой целью введем предикат P( i, j ):
P( i, j ) = A k: 1 ≤ k < j: si+k=pk (1.47)
Наш результирующий индекс, очевидно, должен удовлетворять предикату Р( i, М ). Но этого недостаточно. Поскольку поиск должен обнаружить первое вхождение образа P(k, M) должно быть ложным для всех k < i. Обозначим это условие через Q(i):
Q( i ) = A k: 1 ≤ k < i: ~ P(k,M) (1.48)
Поставленная задача сразу же наводит на мысль оформить поиск как повторяющееся сравнение, и мы предлагаем такой вариант:
i:=0;
REPEAT (* Q(i)*)
i:=i+1;
found:= P(i,M)
UNTIL found OR (i=N-M);
Вычисление Р, естественно, вновь сводится к повторяющимся сравнениям отдельных символов. Если применить к Р теорему Моргана, то получается, что итерации должны «искать» несовпадение между образом и строкой символов.
P( i, j )=( A k: 1 ≤ k < j: si+k=pk )=( ~E k: 1 ≤ k < j: si+k≠pk )
В результате этого уточнения мы приходим к повторению внутри повторения. Предикаты Р и Q включаются в соответствующие места программы как примечания. Они представляют собой инварианты циклов упомянутых итеративных процессов.
i:=0;
REPEAT (* Q(i)*)
i:=i+1; j:=0;
WHILE ( j < M ) AND ( s[ i+j ]=p[ j+1 ] ) DO j:=j+1 (* P( i,j+1) *)
(* Q( i ) & P( i, j ) & (( j=M ) OR ( s[ i+j ] ≠ p[ j ] )) *)
UNTIL ( j=M ) OR ( i=N-M );
В действительности выражение j = M в условии окончания соответствует условию found, так как из него следует P(i, М). Выражение i=N-М имплицирует Q(N—M) и тем самым свидетельствует, что нигде в строке совпадения нет. Если итерации продолжаются при j < М, то они должны продолжаться и при si+k=pk. В соответствии с (1.47) отсюда следует ~P(i, j), затем из-за (1.48) следует Q(i +1), что и подтверждает справедливость Q(i) после очередного увеличения i.
Анализ прямого поиски в строке. Этот алгоритм работает достаточно эффективно, если мы допускаем, что несовпадение пары символов происходит по крайней мере после всего лишь нескольких сравнений во внутреннем цикле. При большой мощности типа item это достаточно частый случай. Можно предполагать, что для текстов, составленных из 128 символов, несовпадение будет обнаруживаться после одной или двух проверок. Тем не менее в худшем случае производительность будет внушать опасение. Возьмем, например, строку, состоящую из N-1 символов А и единственного В, а образ содержит М-1 символов А и опять В. Чтобы обнаружить совпадение в конце строки, требуется произвести порядка N*M сравнений. К счастью, как мы скоро увидим, есть прием, который существенно улучшает обработку этого неблагоприятного варианта.