Добре дошли
В тази статия ще научите как алгоритъмът за двоично търсене работи зад кулисите и как можете да го внедрите в Python.
По-конкретно ще научите:
- Как алгоритъмът работи зад кулисите, за да намери целеви елемент.
- Как работи неговото изпълнение на Python ред по ред.
- Защо това е много ефективен алгоритъм в сравнение с Линейно търсене.
- Неговите предимства и изисквания.
Нека да започнем! ✨
Въведение в двоично търсене
Този алгоритъм се използва за намиране на елемент в подредена последователност (например: списък, кортеж или низ).
Изисквания
За да приложите алгоритъма за двоично търсене към последователност, последователността вече трябва да бъде сортирана във възходящ ред. В противен случай алгоритъмът няма да намери верния отговор. Ако го направи, ще бъде по съвпадение.
💡 Съвет: Можете да сортирате последователността, преди да приложите двоично търсене с алгоритъм за сортиране, който отговаря на вашите нужди.
Вход и изход
Алгоритъмът (реализиран като функция) се нуждае от тези данни:
- Подредена последователност от елементи (например: списък, кортеж, низ).
- Целевият елемент, който търсим.
Той връща индекса на елемента, който търсите, ако е намерен. Ако елементът не бъде намерен, се връща -1.
Ефективност
Той е много ефективен в сравнение с Линейно търсене (търсене на елемент един по един, започвайки от първия), защото ние сме в състояние да "отхвърлим" половината от списъка на всяка стъпка.
Нека започнем да се впускаме в този алгоритъм.
🔸 Визуално разходка
Ще приложим алгоритъма за двоично търсене към този списък:

💡 Съвет: Забележете, че списъкът вече е сортиран. Той включваше индексите като визуална справка.
Цел
Искаме да намерим индекса на цялото число 67 .
Интервал
Нека се преструваме, че ние сме алгоритъмът. Как да започнем процеса?
Започваме с избирането на двете граници на интервала, в който искаме да търсим. Искаме да претърсим целия списък, затова избираме индекс 0
като долна граница и индекс 5
като горна граница:

Среден елемент
Сега трябва да намерим индекса на средния елемент в този интервал. Правим това, като добавяме долната и горната граница и разделяме резултата на 2, като използваме целочислено деление.
В този случай, (0 + 5)//2
е 2
, защото в резултат на 5/2
е 2.5
и целочислено деление отрязва десетичната част.
И така, средният елемент се намира в индекс 2 , а средният е числото 6 :

Сравнения
Сега трябва да започнем да сравняваме средния елемент с нашия целеви елемент, за да видим какво трябва да направим по-нататък.
Ние питаме:
Равен ли е средният елемент на елемента, който търсим?
6 == 67 # False
Не, не е така.
Затова питаме:
По-голям ли е средният елемент от елемента, който търсим?
6 > 67 # False
Не, не е така.
Така че средният елемент е по-малък от елемента, който търсим.
6 < 67 # True
Изхвърлете елементите
Тъй като списъкът вече е сортиран, това ни казва нещо изключително важно. Това ни казва, че можем да „отхвърлим“ долната половина на списъка, защото знаем, че всички елементи, които идват преди средния елемент, ще бъдат по-малки от елемента, който търсим, така че целевият ни елемент не е там.

Започнете отново - изберете границите
Какво да правим по-нататък? Изхвърлихме елементите и цикълът се повтаря отново.
Трябва да изберем границите за новия интервал (виж по-долу). Но забележете, че горната граница се запазва непокътната и се променя само долната граница.

Това е така, защото елементът, който търсим, може да бъде в горната половина на списъка. Горната граница се запазва непокътната, а долната се променя, за да „свие“ интервала до интервал, в който може да бъде намерен целевият ни елемент.
💡 Съвет: Ако средният елемент беше по-голям от елемента, който търсим, горната граница щеше да бъде променена и долната граница щеше да бъде запазена непокътната. По този начин бихме отхвърлили горната половина на списъка и бихме продължили търсенето в долната половина.
Среден елемент
Сега трябва да намерим индекса на средния елемент, като добавим долната граница към горната граница и разделим резултата на 2 с помощта на цяло число деление.
Резултатът от (3+5)//2
е 4
, така че средният елемент се намира в индекс,4
а средният е 67 .

Сравнения
Ние питаме:
Равен ли е средният елемент на елемента, който търсим?
67 == 67 # True
Да, така е! Така че намерихме елемента в индекс 4 . Връща се стойността 4 и алгоритъмът е завършен успешно.
💡 Съвет: Ако елементът не беше намерен, процесът щеше да продължи, докато интервалът вече не беше валиден. Ако елементът не беше намерен в целия списък, -1 щеше да бъде върнат.
Walk Разходка за кодове
Сега, когато имате визуална интуиция за това как алгоритъмът работи зад кулисите, нека се потопим в итеративното изпълнение на Python, като го анализираме ред по ред:
def binary_search(data, elem): low = 0 high = len(data) - 1 while low elem: high = middle - 1 else: low = middle + 1 return -1
Хедър
Тук имаме заглавката на функцията:
def binary_search(data, elem):
Необходими са два аргумента:
- Подредената последователност от елементи (например: списък, кортеж или низ).
- Елементът, който искаме да намерим.
Първоначален интервал
Следващият ред задава началните долни и горни граници:
low = 0 high = len(data) - 1
Началната долна граница е индекс, 0
а началната горна граница е последният индекс на последователността.
Примка
Ще повторим процеса, докато има валиден интервал, докато долната граница е по-малка или равна на горната граница.
while low <= high:
💡 Съвет: Не забравяйте, че границите са индекси.
Среден елемент
При всяка итерация трябва да намерим индекса на средния елемент. За да направите това, добавяме долната и горната граница и разделяме резултата на 2, като използваме целочислено деление.
middle = (low + high)//2
💡 Съвет: Използваме целочислено разделение, в случай че списъкът или интервалът съдържа четен брой елементи. Например, ако списъкът има 6 елемента и не използваме целочислено разделение, middle
резултатът от (0 + 5)/2
който е 2.5
. Индексът не може да бъде плаващ, така че ние съкращаваме десетичната част, като използваме //
и избираме елемента в индекса 2
.
Сравнения
С тези условни условия (вижте по-долу) ние определяме какво да правим в зависимост от стойността на средния елемент data[middle]
. Сравняваме го с целевия елемент, който търсим.
if data[middle] == elem: return middle elif data[middle] > elem: high = middle - 1 else: low = middle + 1
Има три възможности:
- Ако средният елемент е равен на елемента, който търсим, връщаме индекса веднага, защото намерихме елемента.
if data[middle] == elem: return middle
- Ако средният елемент е по-голям от елемента, който търсим, ние преназначаваме горната граница, защото знаем, че целевият елемент е в долната половина на списъка.
elif data[middle] > elem: high = middle - 1
- В противен случай остава единствената опция, че средният елемент е по-малък от елемента, който търсим, затова преназначаваме долната граница, защото знаем, че целевият елемент е в горната половина на списъка.
else: low = middle + 1
Елементът не е намерен
Ако цикълът е завършен, без да се намери елементът, се връща стойността -1.
return -1
и имаме окончателно внедряване на алгоритъма за двоично търсене:
def binary_search(data, elem): low = 0 high = len(data) - 1 while low elem: high = middle - 1 else: low = middle + 1 return -1
🔸 Специални случаи
Това са някои конкретни случаи, които може да откриете, когато започнете да работите с този алгоритъм:
Повтарящи се елементи
Ако търсеният от вас елемент се повтаря в последователността, върнатият индекс ще зависи от броя на елементите и от последователността на операциите, които алгоритъмът изпълнява върху последователността.
>>> >>> b = [2, 2, 3, 6, 7, 7] >>> binary_search(b, 7) 4
Елементът не е намерен
Ако елементът не бъде намерен, се връща -1.
>>> b = [2, 2, 3, 6, 7, 7] >>> binary_search(b, 8) -1
Празна последователност
Ако последователността е празна, ще се върне -1.
>>> b = [] >>> binary_search(b, 8) -1
Несортирана последователност
Ако последователността е несортирана, отговорът няма да е верен. Получаването на правилния индекс е чисто съвпадение и това може да се дължи на реда на елементите в последователността и последователността на операциите, изпълнявани от алгоритъма.
Този пример връща правилния резултат:
>>> b = [5, 7, 3, 0, -9, 2, 6] >>> binary_search(b, 6) 6
Но този не:
>>> b = [5, 7, 3, 0, -9, 2, 10, 6] >>> binary_search(b, 6) -1
💡 Съвет: Помислете защо първият пример връща правилния резултат. Съвет: Чиста случайност е, че редът на елементите се случва така, че алгоритъмът да достигне правилния индекс, но процесът стъпка по стъпка оценява 0
, след това 2
и накрая 6
. В този конкретен случай, за този конкретен елемент се намира правилният индекс, дори ако последователността не е сортирана.
🔹 По-сложен пример
Сега, когато сте по-запознати с алгоритъма и неговото изпълнение на Python, тук имаме по-сложен пример:
Искаме да намерим индекса на елемента 45 в този списък, използвайки двоично търсене:

Първа итерация
Долната и горната граници са избрани:

Избран е средният елемент ( 26 ):

Но средният елемент ( 26 ) не е елементът, който търсим, той е по-малък от 45 :

Второ повторение
Така че можем да отхвърлим всички елементи, които са по-малки от средния елемент и да изберем нови граници. Новата долна граница ( 27 ) е елементът, разположен непосредствено вдясно от предишния среден елемент:

💡 Съвет: Не забравяйте, че списъкът вече е сортиран.
Избран е новият среден елемент ( 30 ):

Средният елемент ( 30 ) не е елементът, който търсим, той е по-малък от 45 :

Трета итерация
Можем да изхвърлим елементите, които са по-малки или равни на 30, които вече не са били изхвърлени. Долната граница се актуализира до 32 :

Тук имаме интересен случай: средният елемент е една от границите на текущия интервал, защото (7+8)//2
е 7
.

Средният елемент ( 32 ) не е елементът, който търсим ( 45 ), той е по-малък.

Четвърто повторение
Можем да изхвърлим елементите, които са по-малки или равни на 32, които вече не са били изхвърлени.
Тук имаме още един много интересен случай: интервалът има само един елемент.

💡 Съвет: Този интервал е валиден, защото написахме това условие while high <= low:
, което включва интервали, при които индексът на долната граница е равен на индекса на горната граница.
Средният елемент е единственият елемент в интервала, защото (8+8)//2
е 8
, така че индексът на средния елемент е 8, а средният елемент е 45 .

Сега средният елемент е елементът, който търсим, 45 :

Така се връща стойността 8 (индексът):
>>> binary_search([1, 3, 7, 15, 26, 27, 30, 32, 45], 45) 8
🔸 Допълнителна практика
Ако искате да имате допълнителна практика с този алгоритъм, опитайте да обясните как работи алгоритъмът зад кулисите, когато е приложен към този списък, за да намерите цялото число 90 :
[5, 8, 15, 26, 38, 56]
- Какво се случва стъпка по стъпка?
- Каква стойност се връща?
- Намерен ли е елементът?
Надявам се наистина да сте харесали статията ми и сте я намерили за полезна. Сега можете да внедрите алгоритъма за двоично търсене в Python. Вижте моя онлайн курс „Алгоритми за търсене и сортиране на Python: Практически подход“. Последвай ме в Туйтър. ⭐️