Двоичный поиск: различия между версиями

Материал из Поле цифровой дидактики
 
 
(не показано 6 промежуточных версий этого же участника)
Строка 1: Строка 1:
[[File:Binary Search Depiction.svg|мини|справа|Визуализация бинарного поиска по массиву. Искомое число — 7.]]
'''Двоичный (бинарный) поиск''' (также известен как '''метод деления пополам''' или '''[[дихотомия]]''') — классический [[алгоритм]] поиска элемента в отсортированном массиве (векторе), использующий дробление массива на половины. Используется в [[информатика|информатике]], [[вычислительная математика|вычислительной математике]] и [[математическое программирование|математическом программировании]].
'''Двоичный (бинарный) поиск''' (также известен как '''метод деления пополам''' или '''[[дихотомия]]''') — классический [[алгоритм]] поиска элемента в отсортированном массиве (векторе), использующий дробление массива на половины. Используется в [[информатика|информатике]], [[вычислительная математика|вычислительной математике]] и [[математическое программирование|математическом программировании]].


Частным случаем двоичного поиска является [[метод бисекции]], который применяется для поиска корней заданной [[непрерывная функция|непрерывной функции]] на заданном отрезке.
== Поиск элемента в отсортированном массиве ==


== Поиск элемента в отсортированном массиве ==
{{Викиучебник|Реализации алгоритмов/Двоичный поиск}}
# Определение значения элемента в середине структуры данных. Полученное значение сравнивается с ключом.
# Определение значения элемента в середине структуры данных. Полученное значение сравнивается с ключом.
# Если ключ меньше значения середины, то поиск осуществляется в первой половине элементов, иначе — во второй.
# Если ключ меньше значения середины, то поиск осуществляется в первой половине элементов, иначе — во второй.
Строка 11: Строка 8:
# Процесс продолжается до тех пор, пока не будет найден элемент со значением ключа или не станет пустым интервал для поиска.
# Процесс продолжается до тех пор, пока не будет найден элемент со значением ключа или не станет пустым интервал для поиска.


Несмотря на то, что код достаточно прост, в нём есть несколько ловушек.
=== Диаграмма бинарного поиска ===
* Код <code>(first + last) / 2</code> ошибочен, если <code>first</code> и <code>last</code> по отдельности умещаются в свой тип, а <code>first+last</code> — нет<ref name="joshua">[http://googleresearch.blogspot.ru/2006/06/extra-extra-read-all-about-it-nearly.html Extra, Extra — Read All About It: Nearly All Binary Searches and Mergesorts are Broken] {{Wayback|url=http://googleresearch.blogspot.ru/2006/06/extra-extra-read-all-about-it-nearly.html |date=20131202222915 }} // Joshua Bloch, Google Research; перевод — [http://habrahabr.ru/post/203398/ Почти во всех реализациях двоичного поиска и сортировки слиянием есть ошибка] {{Wayback|url=http://habrahabr.ru/post/203398/ |date=20131124053301 }}</ref>. Если теоретически возможны массивы столь большого размера, приходится идти на ухищрения:
Алгоритм бинарного поиска в интервале 1 - 12
** Использовать код <code>first + (last - first) / 2</code>, который точно не приведёт к переполнениям, если имеем дело с неотрицательными целыми числами и first<last.
 
*** Если <code>first</code> и <code>last</code> — указатели или [[итератор]]ы, такой код единственно правильный, поскольку не нарушает абстракцию (уже операция «указатель + указатель» некорректна). Разумеется, чтобы сохранялась сложность алгоритма, нужны быстрые операции «указатель+число → указатель», «указатель−указатель → число».
{{#mermaid:graph TB
** Если <code>first</code> и <code>last</code> — типы со знаком, провести расчёт в беззнаковом типе: <code>((unsigned)first + (unsigned)last) / 2</code>. В [[Java]] сработает такой код: <code>(first + last) >>> 1</code> (знаковое двоичное сложение совпадает с беззнаковым, Java гарантирует такое поведение даже при переполнении, и вся эта формула оперирует знаковыми числами как беззнаковыми).
A[1-6] --> |Да| B[1-3]
** Написать расчёт на ассемблере, с использованием [[флаг переноса|флага переноса]]. Что-то наподобие <code>add eax, b; rcr eax, 1</code>. А вот [[длинная арифметика|длинные типы]] использовать нецелесообразно, <code>first + (last - first) / 2</code> быстрее.
A --> |Нет| C[7-9]  
* В двоичном поиске часты [[ошибка на единицу|ошибки на единицу]], и каждая такая ошибка превращается в [[зацикливание]], пропуск или выход за пределы массива. Поэтому важно протестировать такие случаи: пустой массив (<code>n=0</code>), один элемент (<code>n=1</code>), ищем отсутствующее значение (слишком большое, слишком маленькое и где-то в середине), ищем первый и последний элемент.
 
* Иногда требуется, чтобы, если <code>x</code> в цепочке существует в нескольких экземплярах, находило не любой, а обязательно первый (как вариант: последний; либо вообще не <code>x</code>, а следующий за ним элемент).<ref>В [[C++]] <code>std::lower_bound</code> находит первое вхождение <code>x</code>, а <code>std::upper_bound</code> — элемент, следующий за <code>x</code>.</ref> Например, функция {{cpp|std::lower_bound}} из [[C++]] находит первый из равных, а {{cpp|std::upper_bound}} — элемент, следующий за x. Если не найдено — оба возвращают место, куда вставить.
B --> |Да| D[1-2]
B --> |Нет| E[4-5]  
 
D --> |Да| 1
D --> |Нет| 3
1 --> |Нет| 2
 


Учёный [[Бентли, Йон|Йон Бентли]] утверждает, что 90 % студентов, разрабатывая двоичный поиск, забывают учесть какое-либо из этих требований. И даже в код, написанный самим Йоном и ходивший из книги в книгу, вкралась ошибка: код не стоек к переполнениям<ref name="joshua"/>.
E --> |Нет| 6
E --> |Да| 4
4 -->  |Нет| 5
 
C -->|Да| F[7-8] 
C -->|Нет| G[10-11] 
 
F --> |Да| 7
7 --> |Нет| 8
 
F --> |Нет| 9
 
G --> | Да | 10
10 --> | Нет| 11
G --> | Нет| 12
 
}}
 
*  [[Binary search (diagram)]] - варианты с определением месяцев в [[Graphviz]]


== Пример реализации на Java ==
== Пример реализации на Java ==
Строка 49: Строка 70:
* Метод используется для нахождения [[экстремум]]а [[целевая функция|целевой функции]] и в этом случае является [[оптимизация (математика)|методом условной одномерной оптимизации]]. Когда функция имеет вещественный аргумент, найти решение с точностью до <math>\varepsilon</math> можно за время <math>\log_2 1 / \varepsilon</math>. Когда аргумент дискретен, и изначально лежит на отрезке длины ''N'', поиск решения займёт <math>1 + \log_2N</math> времени. Наконец, для поиска экстремума, скажем, для определённости [[Экстремум|минимума]], на очередном шаге отбрасывается тот из концов рассматриваемого отрезка, значение в котором максимально.
* Метод используется для нахождения [[экстремум]]а [[целевая функция|целевой функции]] и в этом случае является [[оптимизация (математика)|методом условной одномерной оптимизации]]. Когда функция имеет вещественный аргумент, найти решение с точностью до <math>\varepsilon</math> можно за время <math>\log_2 1 / \varepsilon</math>. Когда аргумент дискретен, и изначально лежит на отрезке длины ''N'', поиск решения займёт <math>1 + \log_2N</math> времени. Наконец, для поиска экстремума, скажем, для определённости [[Экстремум|минимума]], на очередном шаге отбрасывается тот из концов рассматриваемого отрезка, значение в котором максимально.


== См. также ==
* [[Линейный поиск]]
* [[Метод бисекции]]
* [[Метод дихотомии]]
* [[Метод Ньютона]]
* [[Метод золотого сечения]]
* [[Троичный поиск]]
== Примечания ==
{{примечания}}
== Литература ==
* {{source|Q21694522|part=Глава 4. Метод декомпозиции: Бинарный поиск|pages=180—183}}
* {{книга|автор = Амосов А. А.,  Дубинский Ю. А., Копченова Н. П.|заглавие = Вычислительные методы для инженеров|оригинал = |ссылка 
|издание = |место =  М.|издательство = Мир|год = 1998|страницы =|isbn = }}
* {{книга|автор = Бахвалов Н. С., [[Жидков, Николай Петрович|Жидков Н. П.]], Кобельков Г. Г.|заглавие = Численные методы|оригинал = |ссылка = |издание = 8-е изд|место =  М.|издательство = Лаборатория Базовых Знаний|год = 2000|страницы =|isbn = }}
* {{книга
|автор = Вирт Н.
|часть =
|заглавие = Алгоритмы + структуры данных = программы
|оригинал =
|ссылка =https://archive.org/details/libgen_852
|место =  М.
|издательство = «[[Мир (издательство)|Мир]]»
|год = 1985
|страницы = [https://archive.org/details/libgen_852/page/n31 28]
|isbn =
}}
* {{книга|автор = Волков Е. А.|заглавие = Численные методы|оригинал = |ссылка = |издание =|место =  М.|издательство = Физматлит|год = 2003|страницы =|isbn = }}
* {{книга|автор = Гилл Ф., Мюррей У., Райт М.|заглавие = Практическая оптимизация. Пер. с англ|оригинал = |ссылка = |издание =|место =  М.|издательство = Мир|год = 1985|страницы =|isbn = }}
* {{книга:CLRS|2005}}
* {{книга|автор = Корн Г., Корн Т.|заглавие = Справочник по математике для научных работников и инженеров|оригинал = |ссылка = |издание = |место =  М.|издательство = Наука|год = 1970|страницы = 575-576|isbn = }}
* {{книга|автор = Коршунов Ю. М., Коршунов Ю. М.|заглавие = Математические основы кибернетики|оригинал = |ссылка = |издание =|место  М.|издательство = Энергоатомиздат|год = 1972|страницы =|isbn = }}
* {{книга|автор = Максимов Ю. А., Филлиповская Е. А.|заглавие = Алгоритмы решения задач нелинейного программирования|оригинал = |ссылка = |издание =|место =  М.|издательство = МИФИ|год =  1982|страницы =|isbn = }}
* {{книга
|заглавие = Фундаментальные алгоритмы на C. Анализ/Структуры данных/Сортировка/Поиск
|оригинал = Algorithms in C. Fundamentals/Data Structures/Sorting/Searching
|автор = Роберт Седжвик.
|страницы = 672
|год = 2003
|место =  СПб.
|издательство = ДиаСофтЮП
|isbn = 5-93772-081-4
}}
== Ссылки ==
* [http://algolist.manual.ru Материал на сайте algolist]
* [http://kvodo.ru/dvoichnyiy-poisk.html Пошаговый разбор алгоритма, коды программ]
* [http://habrahabr.ru/post/91698/ Как же все-таки правильно написать двоичный поиск?]
[[Категория:Алгоритмы оптимизации]]
[[Категория:Алгоритмы поиска]]
[[Категория:Алгоритмы поиска]]

Текущая версия на 16:03, 9 ноября 2023

Двоичный (бинарный) поиск (также известен как метод деления пополам или дихотомия) — классический алгоритм поиска элемента в отсортированном массиве (векторе), использующий дробление массива на половины. Используется в информатике, вычислительной математике и математическом программировании.

Поиск элемента в отсортированном массиве

  1. Определение значения элемента в середине структуры данных. Полученное значение сравнивается с ключом.
  2. Если ключ меньше значения середины, то поиск осуществляется в первой половине элементов, иначе — во второй.
  3. Поиск сводится к тому, что вновь определяется значение серединного элемента в выбранной половине и сравнивается с ключом.
  4. Процесс продолжается до тех пор, пока не будет найден элемент со значением ключа или не станет пустым интервал для поиска.

Диаграмма бинарного поиска

Алгоритм бинарного поиска в интервале 1 - 12

Пример реализации на Java

int binarySearch(int[] arr, int key) {
    int low = 0;
    int high = arr.length - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = arr[mid];

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}

Приложения

Практические приложения метода двоичного поиска разнообразны:

  • Широкое распространение в информатике применительно к поиску в структурах данных. Например, поиск в массивах данных осуществляется по ключу, присвоенному каждому из элементов массива (в простейшем случае сам элемент является ключом).
  • Также его применяют в качестве численного метода для нахождения приближённого решения уравнений (см. Метод бисекции).
  • Метод используется для нахождения экстремума целевой функции и в этом случае является методом условной одномерной оптимизации. Когда функция имеет вещественный аргумент, найти решение с точностью до [math]\displaystyle{ \varepsilon }[/math] можно за время [math]\displaystyle{ \log_2 1 / \varepsilon }[/math]. Когда аргумент дискретен, и изначально лежит на отрезке длины N, поиск решения займёт [math]\displaystyle{ 1 + \log_2N }[/math] времени. Наконец, для поиска экстремума, скажем, для определённости минимума, на очередном шаге отбрасывается тот из концов рассматриваемого отрезка, значение в котором максимально.