В информатике бинарный поиск , также известный как поиск по полуинтервалу , [1] логарифмический поиск , [2] или двоичный отрыв , [3] — это алгоритм поиска , который находит положение целевого значения в отсортированном массиве . [4] [5] Двоичный поиск сравнивает целевое значение со средним элементом массива. Если они не равны, половина, в которой не может находиться цель, исключается, и поиск продолжается на оставшейся половине, снова беря средний элемент для сравнения с целевым значением и повторяя это до тех пор, пока целевое значение не будет найдено. Если поиск заканчивается, а оставшаяся половина оказывается пустой, цели нет в массиве.
Двоичный поиск выполняется за логарифмическое время в худшем случае , выполняя сравнения, где — количество элементов в массиве. [a] [6] Двоичный поиск быстрее линейного поиска , за исключением небольших массивов. Однако массив должен быть сначала отсортирован, чтобы можно было применить бинарный поиск. Существуют специализированные структуры данных , предназначенные для быстрого поиска, такие как хэш-таблицы , поиск по которым можно выполнять эффективнее, чем бинарный поиск. Однако бинарный поиск можно использовать для решения более широкого круга задач, таких как поиск следующего наименьшего или следующего наибольшего элемента в массиве относительно целевого, даже если он отсутствует в массиве.
Существует множество вариаций бинарного поиска. В частности, дробное каскадирование ускоряет бинарный поиск одного и того же значения в нескольких массивах. Дробное каскадирование эффективно решает ряд задач поиска в вычислительной геометрии и во многих других областях. Экспоненциальный поиск расширяет бинарный поиск до неограниченных списков. Структуры данных двоичного дерева поиска и B-дерева основаны на двоичном поиске.
Двоичный поиск работает на отсортированных массивах. Двоичный поиск начинается со сравнения элемента в середине массива с целевым значением. Если целевое значение совпадает с элементом, возвращается его позиция в массиве. Если целевое значение меньше элемента, поиск продолжается в нижней половине массива. Если целевое значение больше элемента, поиск продолжается в верхней половине массива. Делая это, алгоритм исключает половину, в которой целевое значение не может находиться в каждой итерации. [7]
При наличии массива элементов со значениями или записями, отсортированными таким образом, что , и целевого значения , следующая подпрограмма использует двоичный поиск для нахождения индекса в . [7]
Эта итеративная процедура отслеживает границы поиска с двумя переменными и . Процедуру можно выразить в псевдокоде следующим образом, где имена и типы переменных остаются такими же, как и выше, является функцией пола и ссылается на конкретное значение, которое передает неудачу поиска. [7]floor
unsuccessful
Функция binary_search(A, n, T) — это Л := 0 Р := n − 1 пока L ≤ R делать м := пол((Д + П) / 2) если А[м] < Т тогда Л := м + 1 иначе если A[m] > T тогда Р := м − 1 в противном случае : возврат m возврат неудачный
В качестве альтернативы алгоритм может взять потолок . Это может изменить результат, если целевое значение встречается в массиве более одного раза.
В вышеприведенной процедуре алгоритм проверяет, равен ли средний элемент ( ) целевому элементу ( ) в каждой итерации. Некоторые реализации опускают эту проверку во время каждой итерации. Алгоритм будет выполнять эту проверку только тогда, когда остается один элемент (когда ). Это приводит к более быстрому циклу сравнения, поскольку одно сравнение исключается за итерацию, в то время как в среднем требуется только одна дополнительная итерация. [8]
Первую реализацию, исключающую эту проверку, Герман Боттенбрух опубликовал в 1962 году. [8] [9]
Где ceil
находится функция потолка, псевдокод для этой версии:
Функция binary_search_alternative(A, n, T) — это Л := 0 Р := n − 1 пока L != R делать м := ceil((L + R) / 2) если А[м] > Т тогда Р := м − 1 еще : Д := м если A[L] = T , то вернуть L вернуть неудачу
Процедура может возвращать любой индекс, элемент которого равен целевому значению, даже если в массиве есть дубликаты элементов. Например, если массив для поиска был , а цель была , то было бы правильным для алгоритма вернуть либо 4-й (индекс 3), либо 5-й (индекс 4) элемент. Обычная процедура вернула бы 4-й элемент (индекс 3) в этом случае. Она не всегда возвращает первый дубликат (подумайте, какой из них все еще возвращает 4-й элемент). Однако иногда необходимо найти самый левый элемент или самый правый элемент для целевого значения, которое дублируется в массиве. В приведенном выше примере 4-й элемент является самым левым элементом значения 4, в то время как 5-й элемент является самым правым элементом значения 4. Альтернативная процедура выше всегда будет возвращать индекс самого правого элемента, если такой элемент существует. [9]
Чтобы найти самый левый элемент, можно использовать следующую процедуру: [10]
Если и , то — самый левый элемент, равный . Даже если нет в массиве, — это ранг в массиве или количество элементов в массиве, которые меньше .
Где floor
находится функция пола, псевдокод для этой версии:
функция binary_search_leftmost(A, n, T): Л := 0 Р := н в то время как L < R: м := пол((Д + П) / 2) если А[м] < Т: Л := м + 1 еще : Р := м возврат Л
Чтобы найти самый правый элемент, можно использовать следующую процедуру: [10]
Если и , то — самый правый элемент, который равен . Даже если нет в массиве, — это количество элементов в массиве, которые больше .
Где floor
находится функция пола, псевдокод для этой версии:
функция binary_search_rightmost(A, n, T): Л := 0 Р := н в то время как L < R: м := пол((Д + П) / 2) если А[м] > Т: Р := м еще : Л := м + 1 возврат Р - 1
Вышеуказанная процедура выполняет только точные совпадения, находя позицию целевого значения. Однако расширение бинарного поиска для выполнения приблизительных совпадений является тривиальным, поскольку бинарный поиск работает с отсортированными массивами. Например, бинарный поиск может быть использован для вычисления для заданного значения его ранга (количества меньших элементов), предшественника (следующего наименьшего элемента), последователя (следующего наибольшего элемента) и ближайшего соседа . Запросы диапазона, ищущие количество элементов между двумя значениями, могут быть выполнены с двумя ранговыми запросами. [11]
С точки зрения количества сравнений производительность бинарного поиска можно проанализировать, просмотрев выполнение процедуры на бинарном дереве. Корневой узел дерева является средним элементом массива. Средний элемент нижней половины является левым дочерним узлом корня, а средний элемент верхней половины является правым дочерним узлом корня. Остальная часть дерева строится аналогичным образом. Начиная с корневого узла, левые или правые поддеревья обходят в зависимости от того, меньше или больше целевое значение рассматриваемого узла. [6] [14]
В худшем случае бинарный поиск выполняет итерации цикла сравнения, где обозначение обозначает функцию пола , которая выдает наибольшее целое число, меньшее или равное аргументу, и является двоичным логарифмом . Это происходит потому, что наихудший случай достигается, когда поиск достигает самого глубокого уровня дерева, а в дереве всегда есть уровни для любого бинарного поиска.
Худший случай также может быть достигнут, когда целевой элемент отсутствует в массиве. Если на единицу меньше степени двойки, то это всегда так. В противном случае поиск может выполнять итерации, если поиск достигает самого глубокого уровня дерева. Однако он может выполнять итерации, что на единицу меньше худшего случая, если поиск заканчивается на втором самом глубоком уровне дерева. [15]
В среднем, предполагая, что каждый элемент с одинаковой вероятностью будет найден, бинарный поиск делает итерации, когда целевой элемент находится в массиве. Это приблизительно равно итерациям. Когда целевой элемент отсутствует в массиве, бинарный поиск делает итерации в среднем, предполагая, что диапазон между и за пределами элементов с одинаковой вероятностью будет найден. [14]
В лучшем случае, когда целевое значение является средним элементом массива, его позиция возвращается после одной итерации. [16]
С точки зрения итераций, ни один алгоритм поиска, который работает только путем сравнения элементов, не может показать лучшую среднюю и худшую производительность, чем бинарный поиск. Дерево сравнения, представляющее бинарный поиск, имеет наименьшее возможное количество уровней, поскольку каждый уровень выше самого нижнего уровня дерева заполнен полностью. [b] В противном случае алгоритм поиска может исключить несколько элементов за итерацию, увеличив количество итераций, требуемых в среднем и худшем случае. Это касается других алгоритмов поиска, основанных на сравнениях, поскольку, хотя они могут работать быстрее на некоторых целевых значениях, средняя производительность по всем элементам хуже, чем у бинарного поиска. Разделив массив пополам, бинарный поиск гарантирует, что размер обоих подмассивов будет максимально схожим. [14]
Двоичный поиск требует три указателя на элементы, которые могут быть индексами массива или указателями на ячейки памяти, независимо от размера массива. Таким образом, пространственная сложность бинарного поиска заключается в модели вычислений Word RAM .
Среднее число итераций, выполняемых бинарным поиском, зависит от вероятности поиска каждого элемента. Средний случай отличается для успешных и неудачных поисков. Предполагается, что каждый элемент с одинаковой вероятностью будет найден для успешного поиска. Для неудачных поисков предполагается, что интервалы между элементами и за их пределами с одинаковой вероятностью будут найдены. Средний случай для успешных поисков — это количество итераций, необходимых для поиска каждого элемента ровно один раз, деленное на , количество элементов. Средний случай для неудачных поисков — это количество итераций, необходимых для поиска элемента внутри каждого интервала ровно один раз, деленное на интервалы. [14]
В представлении двоичного дерева успешный поиск может быть представлен путем от корня до целевого узла, называемым внутренним путем . Длина пути — это количество ребер (соединений между узлами), через которые проходит путь. Количество итераций, выполненных поиском, при условии, что соответствующий путь имеет длину , подсчитывает начальную итерацию. Длина внутреннего пути — это сумма длин всех уникальных внутренних путей. Поскольку существует только один путь от корня до любого отдельного узла, каждый внутренний путь представляет собой поиск определенного элемента. Если есть элементы , что является положительным целым числом, а длина внутреннего пути равна , то среднее количество итераций для успешного поиска , с одной итерацией, добавленной для подсчета начальной итерации. [14]
Поскольку бинарный поиск является оптимальным алгоритмом поиска со сравнениями, эта задача сводится к вычислению минимальной длины внутреннего пути всех бинарных деревьев с узлами, которая равна: [17]
Например, в массиве из 7 элементов корень требует одной итерации, два элемента ниже корня требуют двух итераций, а четыре элемента ниже требуют трех итераций. В этом случае внутренняя длина пути равна: [17]
Среднее число итераций будет основано на уравнении для среднего случая. Сумма для может быть упрощена до: [14]
Подставим уравнение для в уравнение для : [14]
Для целого числа это эквивалентно уравнению для среднего случая успешного поиска, указанному выше.
Неудачные поиски могут быть представлены путем дополнения дерева внешними узлами , что образует расширенное двоичное дерево . Если внутренний узел или узел, присутствующий в дереве, имеет менее двух дочерних узлов, то добавляются дополнительные дочерние узлы, называемые внешними узлами, так что каждый внутренний узел имеет двух дочерних узлов. Таким образом, неудачный поиск может быть представлен как путь к внешнему узлу, родительским элементом которого является единственный элемент, который остается во время последней итерации. Внешний путь — это путь от корня к внешнему узлу. Длина внешнего пути — это сумма длин всех уникальных внешних путей. Если есть элементы , что является положительным целым числом, и длина внешнего пути равна , то среднее число итераций для неудачного поиска , с одной итерацией, добавленной для подсчета начальной итерации. Длина внешнего пути делится на вместо , потому что есть внешние пути, представляющие интервалы между элементами массива и за его пределами. [14]
Эту задачу можно аналогично свести к определению минимальной длины внешнего пути всех бинарных деревьев с узлами. Для всех бинарных деревьев длина внешнего пути равна длине внутреннего пути плюс . [17] Подставляя уравнение для : [14]
Подставляя уравнение для в уравнение для , можно определить средний случай безуспешных поисков: [14]
Каждая итерация процедуры бинарного поиска, определенной выше, выполняет одно или два сравнения, проверяя, равен ли средний элемент целевому элементу в каждой итерации. Предполагая, что каждый элемент с равной вероятностью будет найден, каждая итерация выполняет в среднем 1,5 сравнения. Разновидность алгоритма проверяет, равен ли средний элемент целевому элементу в конце поиска. В среднем это исключает половину сравнения из каждой итерации. Это немного сокращает время, затрачиваемое на итерацию на большинстве компьютеров. Однако это гарантирует, что поиск займет максимальное количество итераций, в среднем добавляя одну итерацию к поиску. Поскольку цикл сравнения выполняется только один раз в худшем случае, небольшое увеличение эффективности на итерацию не компенсирует дополнительную итерацию для всех, кроме очень больших . [c] [18] [19]
При анализе производительности бинарного поиска еще одним соображением является время, необходимое для сравнения двух элементов. Для целых чисел и строк необходимое время увеличивается линейно с увеличением длины кодирования (обычно количества бит ) элементов. Например, сравнение пары 64-битных беззнаковых целых чисел потребует сравнения до двух бит, чем сравнение пары 32-битных беззнаковых целых чисел. Наихудший случай достигается, когда целые числа равны. Это может быть существенным, когда длина кодирования элементов велика, например, в случае больших целочисленных типов или длинных строк, что делает сравнение элементов дорогим. Кроме того, сравнение значений с плавающей точкой (наиболее распространенное цифровое представление действительных чисел ) часто обходится дороже, чем сравнение целых чисел или коротких строк.
В большинстве компьютерных архитектур процессор имеет аппаратный кэш, отдельный от ОЗУ . Поскольку они расположены внутри самого процессора, доступ к кэшам осуществляется намного быстрее, но обычно они хранят гораздо меньше данных, чем ОЗУ. Поэтому большинство процессоров хранят ячейки памяти, к которым недавно был выполнен доступ, вместе с ячейками памяти, близкими к ним. Например, при доступе к элементу массива сам элемент может быть сохранен вместе с элементами, которые хранятся близко к нему в ОЗУ, что ускоряет последовательный доступ к элементам массива, которые близки по индексу друг к другу ( локальность ссылки ). В отсортированном массиве двоичный поиск может переходить к удаленным ячейкам памяти, если массив большой, в отличие от алгоритмов (таких как линейный поиск и линейное зондирование в хэш-таблицах ), которые обращаются к элементам последовательно. Это немного увеличивает время выполнения двоичного поиска для больших массивов в большинстве систем. [20]
Сортированные массивы с бинарным поиском являются очень неэффективным решением, когда операции вставки и удаления чередуются с извлечением, что занимает время для каждой такой операции. Кроме того, отсортированные массивы могут усложнить использование памяти, особенно когда элементы часто вставляются в массив. [21] Существуют другие структуры данных, которые поддерживают гораздо более эффективную вставку и удаление. Двоичный поиск может использоваться для выполнения точного сопоставления и установки членства (определения того, находится ли целевое значение в коллекции значений). Существуют структуры данных, которые поддерживают более быстрое точное сопоставление и установку членства. Однако, в отличие от многих других схем поиска, бинарный поиск может использоваться для эффективного приблизительного сопоставления, обычно выполняя такие сопоставления во времени независимо от типа или структуры самих значений. [22] Кроме того, существуют некоторые операции, такие как поиск наименьшего и наибольшего элемента, которые могут быть эффективно выполнены на отсортированном массиве. [11]
Линейный поиск — это простой алгоритм поиска, который проверяет каждую запись, пока не найдет целевое значение. Линейный поиск можно выполнять в связанном списке, что позволяет выполнять вставку и удаление быстрее, чем массив. Двоичный поиск быстрее линейного поиска для отсортированных массивов, за исключением случаев, когда массив короткий, хотя массив необходимо предварительно отсортировать. [d] [24] Все алгоритмы сортировки, основанные на сравнении элементов, такие как быстрая сортировка и сортировка слиянием , требуют как минимум сравнений в худшем случае. [25] В отличие от линейного поиска, бинарный поиск можно использовать для эффективного приблизительного сопоставления. Существуют такие операции, как поиск наименьшего и наибольшего элемента, которые можно эффективно выполнить для отсортированного массива, но не для несортированного. [26]
Двоичное дерево поиска — это структура данных бинарного дерева , которая работает на основе принципа бинарного поиска. Записи дерева располагаются в отсортированном порядке, и каждая запись в дереве может быть найдена с использованием алгоритма, похожего на бинарный поиск, занимая в среднем логарифмическое время. Вставка и удаление также требуют в среднем логарифмического времени в бинарных деревьях поиска. Это может быть быстрее, чем линейное время вставки и удаления отсортированных массивов, и бинарные деревья сохраняют способность выполнять все операции, возможные на отсортированном массиве, включая диапазонные и приблизительные запросы. [22] [27]
Однако бинарный поиск обычно более эффективен для поиска, поскольку бинарные деревья поиска, скорее всего, будут неидеально сбалансированы, что приведет к немного худшей производительности, чем бинарный поиск. Это применимо даже к сбалансированным бинарным деревьям поиска , бинарным деревьям поиска, которые балансируют свои собственные узлы, потому что они редко производят дерево с наименьшим возможным количеством уровней. За исключением сбалансированных бинарных деревьев поиска, дерево может быть сильно несбалансированным с несколькими внутренними узлами с двумя потомками, что приводит к тому, что среднее и худшее время поиска приближается к сравнению. [e] Бинарные деревья поиска занимают больше места, чем отсортированные массивы. [29]
Двоичные деревья поиска подходят для быстрого поиска во внешней памяти, хранящейся на жестких дисках, поскольку двоичные деревья поиска могут быть эффективно структурированы в файловых системах. B-дерево обобщает этот метод организации дерева. B-деревья часто используются для организации долгосрочного хранения, такого как базы данных и файловые системы . [30] [31]
Для реализации ассоциативных массивов хэш-таблицы , структура данных, которая сопоставляет ключи с записями с помощью хэш-функции , обычно быстрее, чем бинарный поиск в отсортированном массиве записей. [32] Большинству реализаций хэш-таблиц в среднем требуется только амортизированное постоянное время. [f] [34] Однако хэширование бесполезно для приблизительных совпадений, таких как вычисление следующего наименьшего, следующего наибольшего и ближайшего ключа, поскольку единственная информация, предоставляемая при неудачном поиске, заключается в том, что цель отсутствует ни в одной записи. [35] Двоичный поиск идеально подходит для таких совпадений, выполняя их за логарифмическое время. Двоичный поиск также поддерживает приблизительные совпадения. Некоторые операции, такие как поиск наименьшего и наибольшего элемента, могут быть эффективно выполнены в отсортированных массивах, но не в хэш-таблицах. [22]
Связанная с поиском проблема — это членство в наборе . Любой алгоритм, который выполняет поиск, например, бинарный поиск, также может использоваться для членства в наборе. Существуют и другие алгоритмы, которые более конкретно подходят для членства в наборе. Битовый массив является самым простым, полезным, когда диапазон ключей ограничен. Он компактно хранит коллекцию битов , причем каждый бит представляет один ключ в диапазоне ключей. Битовые массивы очень быстры, требуя только времени. [36] Тип Judy1 массива Judy эффективно обрабатывает 64-битные ключи. [37]
Для получения приблизительных результатов фильтры Блума , другая вероятностная структура данных, основанная на хешировании, хранят набор ключей, кодируя ключи с помощью битового массива и нескольких хеш-функций. Фильтры Блума в большинстве случаев гораздо более эффективны с точки зрения пространства, чем битовые массивы, и не намного медленнее: с хеш-функциями запросы на членство требуют только времени. Однако фильтры Блума страдают от ложных срабатываний . [g] [h] [39]
Существуют структуры данных, которые могут улучшить бинарный поиск в некоторых случаях как для поиска, так и для других операций, доступных для отсортированных массивов. Например, поиск, приблизительные совпадения и операции, доступные для отсортированных массивов, могут быть выполнены более эффективно, чем бинарный поиск в специализированных структурах данных, таких как деревья ван Эмде Боаса , деревья слияния , попытки и битовые массивы . Эти специализированные структуры данных обычно работают быстрее только потому, что они используют преимущества свойств ключей с определенным атрибутом (обычно ключей, которые являются небольшими целыми числами), и, таким образом, будут занимать много времени или места для ключей, у которых нет этого атрибута. [22] Пока ключи могут быть упорядочены, эти операции всегда могут быть выполнены по крайней мере эффективно в отсортированном массиве независимо от ключей. Некоторые структуры, такие как массивы Джуди, используют комбинацию подходов для смягчения этого, сохраняя при этом эффективность и возможность выполнять приблизительное совпадение. [37]
Равномерный бинарный поиск хранит вместо нижней и верхней границ разницу в индексе среднего элемента от текущей итерации до следующей итерации. Таблица поиска , содержащая разницы, вычисляется заранее. Например, если массив для поиска — [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] , то средний элемент ( ) будет 6. В этом случае средний элемент левого подмассива ( [1, 2, 3, 4, 5] ) — 3 , а средний элемент правого подмассива ( [7, 8, 9, 10, 11] ) — 9. Равномерный бинарный поиск будет хранить значение 3 , поскольку оба индекса отличаются от 6 на эту же величину. [40] Чтобы уменьшить пространство поиска, алгоритм либо добавляет, либо вычитает это изменение из индекса среднего элемента. Равномерный двоичный поиск может быть быстрее в системах, где вычисление средней точки неэффективно, например, на десятичных компьютерах . [41]
Экспоненциальный поиск расширяет бинарный поиск на неограниченные списки. Он начинается с поиска первого элемента с индексом, который является степенью двойки и больше целевого значения. После этого он устанавливает этот индекс в качестве верхней границы и переключается на бинарный поиск. Поиск занимает итерации до начала бинарного поиска и максимум итерации бинарного поиска, где — позиция целевого значения. Экспоненциальный поиск работает на ограниченных списках, но становится улучшением по сравнению с бинарным поиском только в том случае, если целевое значение находится вблизи начала массива. [42]
Вместо вычисления средней точки, интерполяционный поиск оценивает положение целевого значения, принимая во внимание самые низкие и самые высокие элементы в массиве, а также длину массива. Он работает на основе того, что средняя точка не является лучшим предположением во многих случаях. Например, если целевое значение близко к самому высокому элементу в массиве, оно, скорее всего, будет расположено около конца массива. [43]
Распространенной функцией интерполяции является линейная интерполяция . Если — массив, — нижняя и верхняя границы соответственно, а — цель, то цель оценивается примерно как находящаяся между и . Когда используется линейная интерполяция, а распределение элементов массива равномерное или близкое к равномерному, поиск интерполяции выполняет сравнения. [43] [44] [45]
На практике интерполяционный поиск медленнее бинарного поиска для небольших массивов, поскольку интерполяционный поиск требует дополнительных вычислений. Его временная сложность растет медленнее, чем бинарный поиск, но это компенсирует только дополнительные вычисления для больших массивов. [43]
Дробное каскадирование — это метод, который ускоряет бинарный поиск одного и того же элемента в нескольких отсортированных массивах. Поиск каждого массива по отдельности требует времени, где — количество массивов. Дробное каскадирование сокращает это время за счет хранения в каждом массиве определенной информации о каждом элементе и его положении в других массивах. [46] [47]
Дробное каскадирование изначально было разработано для эффективного решения различных задач вычислительной геометрии . Дробное каскадирование применялось и в других местах, например, в добыче данных и маршрутизации интернет-протокола . [46]
Двоичный поиск был обобщен для работы на определенных типах графов, где целевое значение хранится в вершине, а не в элементе массива. Двоичные деревья поиска являются одним из таких обобщений — когда запрашивается вершина (узел) в дереве, алгоритм либо узнает, что вершина является целью, либо, в противном случае, в каком поддереве будет находиться цель. Однако это можно обобщить еще больше следующим образом: учитывая неориентированный, положительно взвешенный граф и целевую вершину, алгоритм узнает при запросе вершины, что она равна цели, или ему дается инцидентное ребро, которое находится на кратчайшем пути от запрашиваемой вершины до цели. Стандартный алгоритм бинарного поиска — это просто случай, когда граф является путем. Аналогично, бинарные деревья поиска — это случай, когда ребра в левые или правые поддеревья задаются, когда запрашиваемая вершина не равна цели. Для всех неориентированных, положительно взвешенных графов существует алгоритм, который находит целевую вершину в запросах в худшем случае. [48]
Шумные алгоритмы бинарного поиска решают случай, когда алгоритм не может надежно сравнить элементы массива. Для каждой пары элементов существует определенная вероятность того, что алгоритм сделает неправильное сравнение. Шумный бинарный поиск может найти правильное положение цели с заданной вероятностью, которая контролирует надежность полученного положения. Каждая шумная процедура бинарного поиска должна делать по крайней мере сравнений в среднем, где — функция бинарной энтропии , а — вероятность того, что процедура даст неправильное положение. [49] [50] [51] Проблему шумного бинарного поиска можно рассматривать как случай игры Реньи-Улама , [52] вариант Двадцати вопросов , где ответы могут быть неправильными. [53]
Классические компьютеры ограничены худшим случаем точно итераций при выполнении бинарного поиска. Квантовые алгоритмы для бинарного поиска по-прежнему ограничены долей запросов (представляющих итерации классической процедуры), но постоянный множитель меньше единицы, что обеспечивает меньшую временную сложность на квантовых компьютерах . Любая точная квантовая процедура бинарного поиска — то есть процедура, которая всегда дает правильный результат — требует по крайней мере запросов в худшем случае, где — натуральный логарифм . [54] Существует точная квантовая процедура бинарного поиска, которая выполняется в запросах в худшем случае. [55] Для сравнения, алгоритм Гровера является оптимальным квантовым алгоритмом для поиска в неупорядоченном списке элементов, и он требует запросов. [56]
Идея сортировки списка элементов для ускорения поиска восходит к древности. Самым ранним известным примером была табличка Инакибит-Ану из Вавилона, датируемая примерно 200 г. до н. э . Табличка содержала около 500 шестидесятеричных чисел и их обратных чисел, отсортированных в лексикографическом порядке , что облегчало поиск определенной записи. Кроме того, на Эгейских островах было обнаружено несколько списков имен, отсортированных по первой букве . Католикон , латинский словарь, законченный в 1286 г. н. э., был первой работой, описывающей правила сортировки слов в алфавитном порядке, а не только по первым нескольким буквам. [9]
В 1946 году Джон Мочли впервые упомянул о двоичном поиске в рамках лекций школы Мура , основополагающего курса колледжа по информатике. [9] В 1957 году Уильям Уэсли Петерсон опубликовал первый метод интерполяционного поиска. [9] [57] Каждый опубликованный алгоритм двоичного поиска работал только для массивов, длина которых была на единицу меньше степени двойки [i] до 1960 года, когда Деррик Генри Лемер опубликовал алгоритм двоичного поиска, который работал со всеми массивами. [59] В 1962 году Герман Боттенбрух представил реализацию двоичного поиска на ALGOL 60 , которая помещала сравнение на равенство в конец, увеличивая среднее число итераций на одну, но уменьшая до одной число сравнений на итерацию. [8] Равномерный двоичный поиск был разработан А. К. Чандрой из Стэнфордского университета в 1971 году. [9] В 1986 году Бернар Шазелл и Леонидас Дж. Гибас представили дробное каскадирование как метод решения многочисленных задач поиска в вычислительной геометрии . [46] [60] [61]
Хотя основная идея бинарного поиска сравнительно проста, детали могут оказаться на удивление сложными.
— Дональд Кнут [2]
Когда Джон Бентли назначил бинарный поиск в качестве проблемы на курсе для профессиональных программистов, он обнаружил, что девяносто процентов не смогли предоставить правильное решение после нескольких часов работы над ним, в основном потому, что неправильные реализации не запускались или возвращали неправильный ответ в редких крайних случаях . [62] Исследование, опубликованное в 1988 году, показывает, что точный код для него можно найти только в пяти из двадцати учебников. [63] Кроме того, собственная реализация бинарного поиска Бентли, опубликованная в его книге 1986 года Programming Pearls , содержала ошибку переполнения , которая оставалась незамеченной более двадцати лет. Реализация бинарного поиска в библиотеке языка программирования Java имела ту же ошибку переполнения более девяти лет. [64]
В практической реализации переменные, используемые для представления индексов, часто будут иметь фиксированный размер (целые числа), и это может привести к арифметическому переполнению для очень больших массивов. Если середина диапазона вычисляется как , то значение может превышать диапазон целых чисел типа данных, используемого для хранения средней точки, даже если и находятся в пределах диапазона. Если и неотрицательны, этого можно избежать, вычислив среднюю точку как . [65]
Бесконечный цикл может возникнуть, если условия выхода из цикла определены неправильно. Если превышает , поиск не удался и необходимо сообщить об этом. Кроме того, цикл должен быть завершен, когда целевой элемент найден, или в случае реализации, где эта проверка перемещена в конец, должны быть проверки того, был ли поиск успешным или неудачным в конце. Бентли обнаружил, что большинство программистов, которые неправильно реализовали бинарный поиск, допустили ошибку в определении условий выхода. [8] [66]
Стандартные библиотеки многих языков включают процедуры бинарного поиска:
bsearch()
в своей стандартной библиотеке , которая обычно реализуется с помощью бинарного поиска, хотя официальный стандарт этого не требует. [67]binary_search()
, lower_bound()
, upper_bound()
и equal_range()
. [68]std.range
модуле предоставляет тип SortedRange
(возвращаемый функциями sort()
и assumeSorted()
) с методами contains()
, equaleRange()
, lowerBound()
и trisect()
, которые по умолчанию используют методы бинарного поиска для диапазонов, предлагающих произвольный доступ. [69]SEARCH ALL
глагол для выполнения бинарного поиска в упорядоченных таблицах COBOL. [70]sort
содержит функции Search
, SearchInts
, SearchFloat64s
, и SearchStrings
, которые реализуют общий двоичный поиск, а также специальные реализации для поиска фрагментов целых чисел, чисел с плавающей точкой и строк соответственно. [71]binarySearch()
статических методов в классах Arrays
и Collections
в стандартном java.util
пакете для выполнения двоичного поиска в массивах Java и в List
s соответственно. [72] [73]System.Array
метод BinarySearch<T>(T[] array, T value)
. [74]NSArray -indexOfObject:inSortedRange:options:usingComparator:
Mac OS X 10.6+. [75] Фреймворк Core Foundation C от Apple также содержит CFArrayBSearchValues()
функцию. [76]bisect
модуль, который сохраняет список в отсортированном порядке без необходимости сортировать список после каждой вставки. [77]bsearch
включает метод со встроенным приблизительным сопоставлением. [78]binary_search()
обеспечивает , binary_search_by()
, binary_search_by_key()
, и partition_point()
. [79]Эта статья была отправлена в WikiJournal of Science для внешнего академического рецензирования в 2018 году (отчеты рецензентов). Обновленный контент был повторно интегрирован в страницу Википедии по лицензии CC-BY-SA-3.0 ( 2019 ). Версия записи, на которой она была проверена: Anthony Lin; et al. (2 июля 2019 г.). "Алгоритм бинарного поиска" (PDF) . WikiJournal of Science . 2 (1): 5. doi : 10.15347/WJS/2019.005 . ISSN 2470-6345. Wikidata Q81434400.