L:=1; R:=N;
while L<R do
begin
m:=(L+R) div 2;
if a[m]<x then L:=m+1 else R:=m
end;
Умова припинення циклу L>=R досягається. Адже для цілочисельного серединного значення m справедлива нерівність L<=m<R, якщо попередньо виконувалася умова L<R. Отже, або L збільшується при присвоєнні йому значення m+1, або R зменшується при присвоєнні йому значення m. Таким чином, різниця R-L на кожному кроці зменшується, і при досягненні нульового значення (L=R) повторення циклу припиняється.
Варто зауважити, що виконання умови L=R ще не гарантує знаходження потрібного ключа. Потрібно враховувати, що елемент a[R] в порівнянні ніколи участі не бере. Тому необхідна додаткова перевірка після циклу на рівність a[R]=x.
Розглянутий алгоритм як і алгоритм лінійного пошуку знаходить шуканий елемент з найменшим індексом, тобто перше входження в масив. А простий бінарний пошук - довільний із співпадаючих елементів.
Реалізація алгоритму на Turbo Pascal. Нехай дано впорядкований масив елементів цілочисельного типу, знайти заданий елемент і вивести його позицію у масиві використовуючи бінарний пошук.
Program Seach_1_2;
Uses Crt;
Const n=10;
Var a:array [1..n] of integer;
i, k,l,r:integer;
Begin
ClrScr;
For i:=1 to n do
Readln(A[i]);
Writeln(‘Vvedit k’);
Readln(k);
l:=1; r:=n;
while l<r do
begin
i:=(l+r) div 2;
if a[i]<k then l:=i+1
else r:=i
end;
Writeln(‘element ’,k,’ mae index ‘,i+1 );
Readln;
End.
1.3 Пошук по "дереву Фібоначе".
Існує ще один метод подібний до методу описаного вище, проте ефективність його дещо вища ніж пошуку по бінарному дереву, хоча така ж пропорціональність log(2)N. В дереві Фібоначе числа в дочірніх вузлах відрізняються від батьківських на одну і ту ж величину, а саме на число Фібоначе (рис. 2.).
Суть даного метода в тому, що порівнюючи наше шукане значення з наступним значенням в масиві, ми не ділимо навпіл нову зону пошуку, як в бінарному пошуку, а відступаємо від попереднього значення, з яким порівнювали, в потрібну сторону на число Фібоначе.
Рис. 2. Дерево Фібоначе порядку 6
Ефективність даного метода вища за ефективність методу бінарного дерева в тому, що метод Фібоначе включає в себе тільки такі арифметичні операції, як додавання і віднімання, немає необхідності в діленні на 2, тим самим економиться процесорний час. Адже, як відомо виконання операцій віднімання і додавання проходить на порядок швидше ніж операція множення чи ділення.
Даний метод був винайдений в 60-ті роки.
Реалізація алгоритму на Turbo Pascal. Нехай дано впорядкований масив Nелементів цілочисельного типу, знайти заданий елемент і вивести його позицію у масиві використовуючи метод Фібоначе.
Program Seach_1_3;
Uses Crt;
Var a:array [1..200] of integer;
i,n,k,j,l,x,y,z,tmp,q,p:integer;
Begin
ClrScr;
Write('Vvedit kilkist chleniv poslidovnocti');
readln(n);
for j:=1 to 3 do
begin
l:=1;x:=1;y:=0;
While l<trunc(n/2)+1-j do
begin
z:=x; l:=l+1;
x:=x+y; y:=z;
end;
Case j of
1:i:=x;
2:p:=x;
3:q:=3;
end;
end;
Writeln('-----------------------------');
For i:=1 to n do
Readln(A[i]);
Writeln('-----------------------------');
Writeln('Vvedit k');
Readln(k);
while a[i]<>k do
begin
if k<a[i] then begin i:=i-q; tmp:=q; q:=p-q;p:=tmp;end
else begin i:=i+q; p:=p-q; q:=q-p; end;
end;
Writeln('element ',k,' mae index ',i);
Readln;
End.
1.4 Метод екстраполяції
Даний метод в деякій мірі подібний до "методу дихотомії", наведеного вище. Проте, щоб краще зрозуміти даний метод, давайте представимо собі, що нам потрібно взнати, як перекладається на українську мову англійське слово treasure. Тобто в нас задача – знайти це слово в словнику.
По суті, всі наші дії будуть нічим іншим, як реалізацією деякого алгоритму пошуку. Масив значень – це словник, всі значення в ньому відсортовані за алфавітом. Шукане слово нам відомо. Тобто будемо проводити пошук в відсортованому масиві. З першого погляду стає зрозумілим, що ми не будемо перебирати всі слова словника, як і не будем ділити книгу навпіл, дивитися що там в середині і відраховувати в одну і другу сторону рівно ¼ сторінок словника і т.д. (за умови що словник великий). Тобто метод дихотомії і лінійний пошук буде проігноровано.
Більш ніж вірно що ми спочатку відкриємо словник дещо дальше ніж на середині (літера t за серединою латинського алфавіту). Якщо відкрили на літері R, ясно, що шукати потрібно в другій частині словника, а на скільки відступити? На половину? "Навіщо ж на половину, це більше, чим потрібно", скажете ви і будете праві. Адже нам не просто відомо, в якій частині масиву шукане значення, ми володіємо ще і інформацією про те наскільки далеко потрібно зробити наступний крок!
Ось ми і підійшли до суті розглядуваного методу. На відміну від перших двох методів, він не лише визначає зону нового пошуку, а і оцінює величину кроку. Алгоритм називається екстраполяційний метод і має швидкість сходження більшу, ніж метод поділу навпіл.
Якщо при пошуку по бінарному дереву за кожний крок масиву пошуку зменшується з N до значення N/2, то при цьому методі за кожен крок зона зменшується з N значення до кореня квадратного з N. Якщо K лежить в межах Kn і Km, то наступний крок робимо від n на величину (n - m)*(K - Kn)/(Km - Kn). Швидкість екстраполяційного методу розпочинає суттєво перевершувати швидкість метода половинного ділення при великих значеннях.
Реалізація алгоритму на Turbo Pascal. Нехай дано впорядкований масив Nелементів цілочисельного типу, знайти заданий елемент і вивести його позицію у масиві використовуючи екстраполяційний метод.
Program Seach_1_4;
Uses Crt;
Var a:array [1..200] of integer;
i,j,n,k,l,r:integer;
Begin
ClrScr;
Write('Vvedit kilkist chleniv poslidovnocti = ');
readln(n);
Writeln('-----------------------------');
For i:=1 to n do Readln(A[i]);
Writeln('-----------------------------');
Writeln('Vvedit k');
Readln(k);
l:=1; r:=n; i:=1;
while r>l do
begin
i:=(((r-l)*(K-a[l])) div (a[r]-a[l]));
if (K<a[i])then r:=i-1
else l:=i+1;
if K=a[i] then Writeln('element ',k,' mae index ',l);
end;
readln;
End.
Розділ ІІ. Пошук підрядка в рядку
2.1 Прямий пошук підрядка
Часто доводиться зустрічатися із специфічним пошуком - так званим пошуком рядка. Мається на увазі визначення позиції в одній послідовності елементів, починаючи з якої інша послідовність повністю в неї входить. Така операція часто зустрічається в задачах обробки текстів.
Нехай задано масив a із N елементів та масив b із M елементів, які називаються відповідно базовим рядком та підрядком або образом, причому
:a : array [1..N] of basetype ; b : array [1..M] of basetype ;
Пошук рядка передбачає встановлення першого входження образа b в базовий рядок a.
Прямий пошук полягає в повторюваному порівнянні елементів двох масивів. У випадку неспівпадання чергової пари елементів образ зсувається на одну позицію вздовж базового масиву і процес порівняння проводиться знову починаючи з першого елемента шуканого підрядка.
x y z t u x y t
x y t
x y z t u x y t
x y t
x y z t u x y t
x y t
x y z t u x y t
x y t
x y z t u x y t
x y t
x y z t u x y t
x y t
Процес порівняння елементів може припинитися при виконанні однієї із двох умов :
всі елементи образа співпали з відповідними елементами бази - це означає, що позиція входження встановлена ;
після чергового неспівпадання елементів та зсуву образа відбувся вихід підрядка за межі бази - це означає, що шуканий підрядок не входить в базовий.
Якщо позначити біжучий індекс по базі через i , а індекс по образу через j , то ці умови можна записати у вигляді диз’юнкції логічних виразів (j>M) or (i>N-M+1) .
Таким чином програмна реалізація прямого пошуку підрядка в рядку матиме вигляд процедури :
Var
i, j, k : integer;
Begin
i:=1; j:=1; k:=0;
while (j<=M) and (i<=N-M+1) do
begin
j:=k+1; k:=j; i:=1;
while a[j]=b[i] do
begin inc(v); i:=i+1; j:=j+1; end;
end;
if i>m then writeln('номер позиції входження ',(j+1)-i,' важких операцій виконується N=',v+w )
else writeln('не має входження образу в базу, важких операцій виконується N=',v+w);
readln;
END.
Просто і елегантно, вроді би так і потрібно. Дійсно, це не важко у виконанні, але і не дуже ефективно на практиці. Давайте проведемо оцінку швидкості роботи цього алгоритму. В ньому присутні два цикли (один вкладений), час роботи зовнішнього циклу більшою мірою залежить від n, а внутрішній в гіршому випадку дає m операцій. Таким чином, час роботи всього алгоритму рівний O((n-m+1)m). Для малих рядків пошук пройде досить швидко, але якщо в деякому багатомегабайтному файлі буде потрібно знайти послідовність довжиною 100 Кб, то час затрачений на пошук буде дуже великий.Основним недоліком даного методу є те, що приходиться виконувати багато "лишньої" роботи. Наприклад, знайшовши рядок aabc і виявивши невідповідність в четвертому символі (співпало тільки aab), алгоритм буде продовжувати порівнювати рядок, починаючи із наступного символу, хоча це однозначно не приведе до вірного результату. Даний алгоритм найбільш придатний до невеликого об’єму тексту.