#Бинарный поиск
Про бинарный поиск можно говорить очень долго, так как этот алгоритм несмотря на свою простоту обладает поистине мощным применением, и сама идея, лежащая в основе, тоже является незаменимой в багаже знаний любого программиста.
Начнем мы с небольшого описания алгоритма. Суть бинарного поиска в том, что мы делим какое-то множество на две примерно равные части и, анализируя элемент по середине, отбрасываем либо первую, либо вторую половину, далее продолжаем поиск рекурсивно в выбранной части множества. Уже можно догадаться, что алгоритм работает только тогда, когда можно выбрать этот "средний" элемент - тем самым необходима, например, упорядоченность рассматриваемого множества. Чтобы разобраться лучше, сейчас мы рассмотрим несколько задач, для которых бинарный поиск крайне эффективен, а иногда является единственным решением.
###Поиск элемента в массиве
Пусть нам дан упорядоченный массив, который в процессе не будет изменяться. И допустим, к нему приходит множество запросов на проверку того, что лежит ли какой-то элемент массиве или нет. Эту задачу мы решим с помощью бинарного поиска, который намного быстрее обычного линейного поиска, работающего в худшем случае за
Итак, суть все та же - выбираем средний элемент в массиве, сравниваем его с нужным элементом и отбрасываем либо левую часть массива, либо правую, потом запускаем ту же процедуру над нужной половиной. В конце мы придем к массиву длинной 1, и нам останется проверить только равенство этого одного элемента с данным. Приведем главную часть кода:
# массив array отсортирован по возрастанию
def bin_search(array, elem):
# если массив пустой, то никакого элемента там точно нет
if len(array) < 1:
return False
# самый простой случай - массив состоит из одного элемента
# сравниваем этот элемент с нужным
if len(array) == 1:
return array[0] == elem
# тут идет выбор "среднего"
m = len(array) // 2
# если мы уже нашли элемент, то почему бы сразу не понять это
if array[m] == elem:
return True
# сравниваем "средний" элемент с данным
# и понимаем какую половину массива следует нам отбросить
if array[m] < elem:
# если "средний" элемент меньше нужного, мы отбрасываем левую половину
# так как все элементы в ней заведомо меньше
return bin_search(array[m + 1:], elem)
else:
# аналогично для правой, где все элементы больше
return bin_search(array[:m], elem)
Эту рекурсию естественным образом можно переписать в цикл, который будет работать оптимальнее по времени и памяти.
# массив опять отсортирован по возрастанию
def bin_search(array, elem):
# теперь мы будем поддерживать отрезок индексов [l, r),
# в котором должен быть нужный элемент
# если элемент есть в массиве, то он точно будет в этом отрезке
l = 0
r = len(array)
# если отрезок с самого начала пустой, то нашего элемента там точно нет
if r == 0:
return False
# сужаем наш отрезок, пока не останется один элемент
while l <= r:
# как обычно выбираем "средний" элемент и сравниваем его с нужным
m = (l + r) / 2
# если средний элемент меньше, отбрасываем левую половину
# сдвигая левый конец отрезка l в середину m
if array[m] <= elem:
l = m
else:
# аналогично, сдвигаем правый конце в середину
# отбрасывая правую половину
r = m
# и в конце проверяем содержит ли наш отрезок нужный элемент
return array[l] == elem
Настало время исследовать скорость работы данного алгоритма. Допустим мы запускаем поиск на массиве длины
###Поиск значения функции на отрезке
Перейдем теперь к следующей задаче, в которой бинарный поиск по-настоящему незаменим. Рассмотрим монотонную функцию (для простоты она будет возрастать) на каком-нибудь отрезке, и опять требуется искать на этом отрезке определенные значения.
Сразу же можно представить, что у нас есть бесконечный отсортированный массив, в котором индексы - это точки на отрезке, а содержимое массива - значения функции, только этот массив задан не явно, а посредством формулы. После этого наблюдения это задачу легко свести к бинарному поиску, но с одним замечанием: так как массив у нас бесконечный, то и бинарный поиск будет работать бесконечно. Эту проблему обычно обходят с помощью задания желаемой точности ответа, либо делают какое-то заданное количество итераций алгоритма. Первый вариант более гибкий, а второй более точный из-за вечной проблемы со сравнением вещественных чисел в компьютере. Приведу реализацию первого случая, но его будет легко переписать и с учетом второго условия.
# (l, r) - отрезок на котором нужно найти такое x, что f(x) = y
def bin_search(l, r, f, y):
# задаем нужную точность ответа
eps = 1e-10
# ответ точно лежит, между l и r, если разница между ними станет меньше eps,
# то полученное нами число будет отличатся от ответа не больше чем на eps
while abs(r - l) > eps:
# как обычно делим отрезок пополам и сравниваем середину с x,
# отбрасывая ненужную половину
m = (r + l) / 2
if f(m) < y:
l = m
else:
r = m
#возвращаем число внутри отрезка
return (r + l) / 2
В данном случае асимптотика будет равна