- •Основные алгоритмы
- •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.2. Поиск делением пополам (двоичный поиск)
Совершенно очевидно, что других способов убыстрения поиска не существует, если, конечно, нет еще какой-либо информации о данных, среди которых идет поиск. Хорошо известно, что поиск можно сделать значительно, более эффективным, если данные будут упорядочены. Вообразите себе телефонный справочник, в котором фамилии не будут расположены по порядку. Это нечто совершенно бесполезное! Поэтому мы приводим алгоритм, основанный на знании того, что массив а упорядочен, т. е. удовлетворяет условию
A k: 1 ≤ k ≤ N: ak-1 ≤ ak (1.39)
Основная идея—выбрать случайно некоторый элемент, предположим am, и сравнить его с аргументом поиска х. Если он равен х, то поиск заканчивается, если он меньше х, то мы заключаем, что все элементы с индексами, меньшими или равными m, можно исключить из дальнейшего поиска; если же он больше х, то исключаются индексы больше и равные т. Это соображение приводит нас к следующему алгоритму (он называется «поиском делением пополам»). Здесь две индексные переменные L и R отмечают соответственно левый и правый конец секции массива а, где еще может быть обнаружен требуемый элемент.
L:=1; R:=N; found:=FALSE; (1.40)
WHILE (L<=R) and NOT found DO
BEGIN
m:=(L+R) DIV 2; { выбрираем любой элемент между элементами с номерами L и R }
IF a[ m ]=x THEN found:=TRUE ELSE
IF a[ m ]<x THEN L:=m+1 ELSE R:=m-1;
END;
If found then writeln( ‘искомый элемент ‘,a [ m ] );
Инвариант цикла, т. е. условие, выполняющееся перед каждым шагом, таков:
( L ≤ R ) & ( A k: 1 ≤ k < L : ak < х ) & ( A k: R < k ≤ N : ak > х ) (1.41)
из чего выводится результат
found OR (( L > R ) & ( A k: 1 ≤ k < L : ak < х ) & ( A k: R < k ≤ N : ak > х ))
откуда следует
( am = x ) OR ( A k: 1 ≤ k ≤ N : ak ≠ х ).
Выбор m совершенно произволен в том смысле, что корректность алгоритма от него не зависит. Однако на его эффективность выбор влияет. Ясно, что наша задача — исключить на каждом шагу из дальнейшего поиска, каким бы ни был результат сравнения, как можно больше элементов. Оптимальным решением будет выбор среднего элемента, так как при этом в любом случае будет исключаться половина массива. В результате максимальное число сравнений равно log2(N), округленному до ближайшего целого. Таким образом, приведенный алгоритм существенно выигрывает по сравнению с линейным поиском, ведь там ожидаемое число сравнений - N/2.
Эффективность можно несколько улучшить, поменяв местами заголовки условных операторов. Проверку на равенство можно выполнять во вторую очередь, так как она встречается лишь единожды и приводит к окончанию работы. Но более существенно следующее соображение: нельзя ли, как и при линейном поиске, отыскать такое решение, которое опять бы упростило условие окончания. И мы действительно находим такой быстрый алгоритм, как только отказываемся от наивного желания кончить поиск при фиксации совпадения. На первый взгляд это кажется странным, однако при внимательном рассмотрении обнаруживается, что выигрыш в эффективности на каждом шаге превосходит потери от сравнения с несколькими дополнительными элементами. Напомним, что число шагов в худшем случае – log2(N). Быстрый алгоритм основан на следующем инварианте:
( A k: 1 ≤ k < L : ak < х ) & ( A k: R < k ≤ N : ak ≥ х ) (1.42)
причем поиск продолжается до тех пор, пока обе секции не «накроют» массив целиком.
L:=1; R:=N+1;
WHILE L<R DO
BEGIN
j:= (L+R) DIV 2;
IF a [ j ] < x THEN L:=j+1 ELSE R:=j;
END;
IF a [ R ] = x THEN writeln( ‘искомый элемент ‘,a [ R ] );
Условие окончания – L ≥ R, но достижимо ли оно? Для доказательства этого нам необходимо показать, что пр и всех обстоятельствах разность R - L на каждом шаге убывает. В начале каждого шага L < R. Для среднего арифметического m справедливо условие L ≤ m < R. Следовательно, разность действительно убывает, ведь либо L увеличивается при присваивании ему значения m + 1, либо R уменьшается при присваивании значения m. При L = R повторение цикла заканчивается. Однако наш инвариант и условие L = R еще не свидетельствуют о совпадении. Конечно, при R = N+1 никаких совпадений нет. В других же случаях мы должны учитывать, что элемент а [ R ] в сравнениях никогда не участвует. Следовательно, необходима дополнительная проверка на равенство а [ R ] = х. В отличие от первого нашего решения (1.40) приведенный алгоритм, как и в случае линейного поиска, находит совпадающий элемент с наименьшим индексом.