Статьи

API двоичного поиска Java за пять минут

Бинарный поиск — это очень эффективный алгоритм поиска, который находит элемент в отсортированном массиве путем многократного сокращения диапазона поиска вдвое. В результате время выполнения является логарифмическим, или O (log n ). В этой статье рассказывается, как использовать Java Arrays.binarySearch и Collections.binarySearch для поиска в отсортированных массивах или списках массивов значительно быстрее, чем при использовании методов линейного поиска, таких как цикл for или List.contains .

Как минимум, требуются только рабочие знания массивов и списков . Знание Comparable и Comparator пригодится.

Поиск с помощью бинарного поиска

Функция двоичного поиска Java может быть найдена в API java.util.Arrays и java.util.Collections . Arrays API предоставляет реализацию для массивов.

Поиск в массиве

В простейшей форме статический метод Arrays.binarySearch принимает массив любого примитивного типа (а также объект или ссылочный тип) и ключ поиска, например:

Методы возвращают int представляющий индекс найденного поискового ключа в массиве, или иначе отрицательное значение (значение которого я объясню через минуту), если оно не найдено.

Вот пример кода:

 int[] numbers = {2, 3, 5, 7}; System.out.println("index of 5 is " + Arrays.binarySearch(numbers, 5)); // index of 5 is 2 

Перед использованием функции двоичного поиска убедитесь, что массив уже отсортирован в порядке возрастания (например, с Arrays.sort метода Arrays.sort ), в противном случае результаты будут неожиданными.

Поиск диапазона

Если вы должны искать только в диапазоне указанного массива ключ поиска, вы можете использовать эту перегрузку:

Обратите внимание, что toIndex является эксклюзивным для поиска. В предыдущем примере, если вы хотите искать в массиве только индекс от 0 до 2 (включительно), вы пишете:

 Arrays.binarySearch(numbers, 0, 3, 5); 

Поиск с помощью компаратора

Если вы используете метод двоичного поиска для массива Object , элементы массива должны либо реализовать интерфейс Comparable либо должен быть указан Comparator :

Объект Comparator используется двоичным поиском для сравнения порядка двух объектов, чтобы он мог определить, находится ли объект перед или после другого в массиве во время поиска. Другими словами, API-интерфейсы не ограничивают массив Object в возрастающем / естественном порядке.

Массив может быть в любом порядке сортировки, если вы можете предоставить Comparator который устанавливает порядок, соответствующий порядку сортировки массива. В простейшем случае вы использовали один и тот же компаратор для сортировки массива с помощью Arrays.sort(T[] a, Comparator<? super T> c) .

Вот пример для поиска в массиве Integer в порядке убывания:

 Integer[] numbers = {2, 3, 5, 7}; // sort numbers into reverse natural order, ie {7, 5, 3, 2} Arrays.sort(numbers, Collections.reverseOrder()); // search using a reverse natural order comparator System.out.println("index of 5 is " + Arrays.binarySearch(numbers, 5, Collections.reverseOrder())); // index of 5 is 1 

Поиск в списке

Класс Collections Java, с другой стороны, предоставляет функцию двоичного поиска для работы со List . Две версии перегруженного метода Collections.binarySearch :

Использование этих методов аналогично Arrays.binarySearch за исключением того, что методы принимают List объектов, а не массив.

Вот пример:

 List<Integer> numbers = Arrays.asList(2, 3, 5, 7); System.out.println("index of 5 is " + Collections.binarySearch(numbers, 5)); // index of 5 is 2 

Предостережение: неразумно использовать бинарный поиск в LinkedList , реализация которого позволяет только последовательный доступ к его элементам, в результате чего алгоритм бинарного поиска работает очень плохо.

Бинарный поиск для вставки

API-интерфейсы как Arrays.binarySearch и Collections.binarySearch имеют особое поведение, когда они не могут найти данный ключ поиска в указанном массиве или списке: в этом случае метод двоичного поиска в обоих классах возвращает отрицательное значение, эквивалентное - <insertion point> - 1 , где точка вставки определяется как индекс, по которому ключ поиска будет вставлен в массив или список.

Вот пример:

 List<Integer> numbers = new ArrayList<>( Arrays.asList( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29)); System.out.println("index of 5 is " + Collections.binarySearch(numbers, 5)); System.out.println("index of 23 is " + Collections.binarySearch(numbers, 23)); System.out.println("index of 9 is " + Collections.binarySearch(numbers, 9)); System.out.println("index of 31 is " + Collections.binarySearch(numbers, 31)); 

Выход:

 index of 5 is 2 index of 23 is 8 index of 9 is -5 index of 31 is -11 

В этом примере целые числа 9 и 31 не существуют в списке. С возвращенным отрицательным значением 9 будет вставлено в индекс 4 , толкая исходные записи в индексах 4 и после одного слота назад. Значение 31 было бы вставлено в индекс 10 , который находится в конце списка. Если бы мы вставили 9 в список, мы могли бы написать такой код:

 int pos = Collections.binarySearch(numbers, 9); if (pos < 0) { numbers.add(Math.abs(pos)-1, 9); } 

Эта функция бинарного поиска особенно полезна для реализации функции вставки, например, для некоторой очереди приоритетов, которая использует массив для хранения своих записей в порядке возрастания. Точку вставки можно использовать для соответствующей вставки новой записи в очередь.

Бинарный поиск по массиву или списку

Завершение

Стандартные методы двоичного поиска Java для java.util.Arrays и java.util.Collections позволяют найти элемент или найти точку вставки для нового элемента в массиве или списке. Бинарный поиск работает только по отсортированным массивам или спискам. Он имеет логарифмическое время выполнения, что делает его очень эффективным для больших структур данных: учитывая отсортированный массив размером 100 000, максимальные необходимые шаги для бинарного поиска для поиска элемента — это log (100 000) / log (2) ≈ 17. На небольших данных структуры, дополнительные издержки могут сделать двоичный поиск медленнее, чем линейное сканирование.