Arrays.binarySearch: примеры (JAVA)
Arrays.binarySearch(int[] a, int key): intОписание и поведение Arrays.binarySearch()
Метод java.util.Arrays.binarySearch() выполняет бинарный поиск заданного ключа в упорядоченном массиве. Для корректного результата массив должен быть предварительно отсортирован в порядке, согласованном с используемым сравнителем или естественным порядком элементов. Метод доступен для массивов примитивных типов и объектов и имеет несколько перегрузок, в том числе с указанием диапазона поиска.
Общие семантика возвращаемого значения: если ключ найден, возвращается индекс одного из совпадающих элементов (не гарантируется первый или последний при наличии дубликатов). Если ключ не найден, возвращается отрицательное значение, равное -(insertionPoint) - 1, где insertionPoint - позиция, по которой ключ должен быть вставлен, чтобы сохранялся порядок.
Перечень основных перегрузок и аргументов:
binarySearch(byte[] a, byte key),binarySearch(short[] a, short key),binarySearch(char[] a, char key),binarySearch(int[] a, int key),binarySearch(long[] a, long key),binarySearch(float[] a, float key),binarySearch(double[] a, double key)- поиск в полном массиве примитивов.- Для тех же типов существуют перегрузки с диапазоном:
binarySearch(type[] a, int fromIndex, int toIndex, type key), где поиск выполняется в полузакрытом диапазоне [fromIndex, toIndex). binarySearch(Object[] a, Object key)- поиск по естественному порядку (элементы должны реализоватьComparable),binarySearch(Object[] a, Object key, Comparator super T> c)- поиск с пользовательскимComparator,- Аналогичные перегрузки для диапазона:
binarySearch(Object[] a, int fromIndex, int toIndex, Object key)иbinarySearch(Object[] a, int fromIndex, int toIndex, Object key, Comparator super T> c).
Поведение в особых случаях:
- Если массив не отсортирован в соответствии с используемым сравнителем, результат неопределённый.
- Для объектов возможны
ClassCastException, если сравнение некорректно (например, разные типы в массиве без подходящего компаратора). NullPointerExceptionвозникает, если переданnullвместо массива; при использовании компаратора он не должен быть null, если перегрузка требует компаратора.- Для типов с плавающей запятой следует учитывать особенности сравнения (NaN, знаковый ноль) - фактическое поведение определяется реализацией сравнения в Java (например,
Float.compare,Double.compare).
Короткие примеры использования
Примеры демонстрируют основные варианты: поиск в отсортированном массиве, поиск с отрицательным результатом, поиск в диапазоне и поиск объектов с компаратором.
Поиск в массиве int
int[] a = {1, 3, 5, 7, 9};
int idx = java.util.Arrays.binarySearch(a, 5);
System.out.println(idx);
2
Ключ не найден - отрицательное значение (insertion point)
int[] a = {1, 3, 5, 7, 9};
int idx = java.util.Arrays.binarySearch(a, 4);
System.out.println(idx); // -(insertionPoint) - 1
-3
Поиск в диапазоне
int[] a = {1, 2, 3, 4, 5, 6};
int idx = java.util.Arrays.binarySearch(a, 2, 5, 4); // ищет в a[2..4]
System.out.println(idx);
-4 // элемент 4 находится по индексу 3, но вне указанного диапазона, поэтому не найден
Поиск объектов с компаратором
String[] s = {"apple", "banana", "pear"};
java.util.Comparator comp = java.util.Comparator.naturalOrder();
int idx = java.util.Arrays.binarySearch(s, "banana", comp);
System.out.println(idx);
1
Поиск в несортированном массиве (неопределённый результат)
int[] a = {5, 1, 9, 3};
int idx = java.util.Arrays.binarySearch(a, 3);
System.out.println(idx);
(результат неопределённый, зависит от порядка элементов; например, -2)
Похожие средства в Java
Несколько альтернатив и их особенности:
- Collections.binarySearch(List, key)
- NavigableSet / TreeSet
- Самостоятельная реализация двоичного поиска
Работает с List (например, ArrayList). Похожий контракт возвращаемого значения; требует, чтобы список был отсортирован по тому же сравнению. Удобен для коллекций, но медленнее при каждом обращении к произвольному элементу у списков с линейным доступом.
Наборы типа TreeSet хранят элементы в отсортированном виде и предоставляют методы (floor, ceiling, higher, lower) для поиска ближайших значений. Предпочтительнее при частых вставках/удалениях и поисках.
Иногда удобна для специфичных требований (поиск первого вхождения, работа с пользовательной логикой сравнения). При необходимости контроля над алгоритмом даёт гибкость.
Аналоги в других языках и отличия
- Python (модуль bisect)
Функции bisect_left, bisect_right возвращают позицию вставки. Для проверки наличия элемента обычно: i = bisect_left(a, x); found = i < len(a) and a[i] == x.
import bisect
a = [1, 3, 5, 7]
i = bisect.bisect_left(a, 5)
print(i, a[i] == 5)
2 True
Поведение и контракт возвращаемого значения близки к Java (возвращает индекс или отрицательное значение, выражающее точку вставки). Поддерживает перегрузки для диапазона и компараторов.
int[] a = {1,3,5,7};
int idx = Array.BinarySearch(a, 5);
Console.WriteLine(idx);
2
В Go используется sort.Search, принимающий функцию-предикат. Возвращает позицию вставки; для проверки равенства требуется дополнительная проверка.
import "sort"
a := []int{1,3,5,7}
i := sort.Search(len(a), func(i int) bool { return a[i] >= 5 })
fmt.Println(i, i<len(a) && a[i]==5)
2 true
В стандартной библиотеке нет бинарного поиска. Часто используют собственную реализацию или вспомогательные библиотеки (например, Lodash: _.sortedIndexOf, _.sortedIndex).
// пример с Lodash
_.sortedIndex([1,3,5,7], 5) // позиция вставки
2
Стандартных бинарных функций нет, обычно выполняется sort и затем собственный бинарный поиск.
В базах данных поиск выполняется через индексы и план выполнения; прямого аналога функции нет, но индекс обеспечивает логарифмическое время поиска в таблице.
В Kotlin доступны функции Arrays.binarySearch и расширения для коллекций, поведение идентично Java, но с синтаксическими улучшениями языка и возможностью использования лямбда-компараторов.
Нет встроенного бинарного поиска, реализуется вручную.
Типичные ошибки и примеры
Ниже перечислены частые ошибки при использовании Arrays.binarySearch и иллюстрации их проявления.
Неотсортированный массив - неожиданный результат
int[] a = {10, 1, 8, 3};
int idx = java.util.Arrays.binarySearch(a, 3);
System.out.println(idx);
(результат неопределённый, например -2)
Неправильная интерпретация отрицательного кода
int[] a = {1,3,5,7};
int r = java.util.Arrays.binarySearch(a, 4);
int insertionPoint = -r - 1; // правильно
System.out.println(r + ", " + insertionPoint);
-3, 2
Использование неконсистентного Comparator
String[] s = {"a","B","c"};
java.util.Comparator comp = (x,y) -> x.length() - y.length();
java.util.Arrays.sort(s, comp);
int idx = java.util.Arrays.binarySearch(s, "B", comp);
System.out.println(idx);
(результат может быть неожиданным, так как Comparator не даёт тот же порядок, что естественное сравнение; при смешанных критериях возможны логические ошибки)
ClassCastException при смешанных типах в Object[]
Object[] a = {"one", 2};
int idx = java.util.Arrays.binarySearch(a, "one");
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Изменения и история
Метод является частью библиотеки с ранних версий Java и существенно не менялся в семантике. Начиная с Java 5–8 появились удобства языка (дженерики, лямбда-выражения), которые упростили использование перегрузок с Comparator. Для типов с плавающей запятой остаётся поведение, базирующееся на Float.compare и Double.compare. В последних релизах изменений API для Arrays.binarySearch не наблюдалось.
Расширенные и редкие варианты применения
Нахождение первой позиции вхождения среди дубликатов
int[] a = {1,2,2,2,3,4};
int idx = java.util.Arrays.binarySearch(a, 2);
// найден некоторый индекс, но нужно найти первый
if (idx >= 0) {
while (idx > 0 && a[idx-1] == 2) idx--;
}
System.out.println(idx);
1
Нахождение соседних элементов (floor/ceiling) используя insertion point
int[] a = {1,3,5,7};
int r = java.util.Arrays.binarySearch(a, 4);
int ip = -r - 1; // insertion point
int floor = (ip - 1 >= 0) ? a[ip-1] : Integer.MIN_VALUE;
int ceiling = (ip < a.length) ? a[ip] : Integer.MAX_VALUE;
System.out.println("floor=" + floor + ", ceiling=" + ceiling);
floor=3, ceiling=5
Поиск с пользовательским компаратором для объектов
class Person {
String name; int age;
Person(String n, int a){name=n; age=a;}
public String toString(){return name+":"+age;}
}
Person[] arr = {new Person("A",30), new Person("B",20), new Person("C",25)};
java.util.Comparator byAge = java.util.Comparator.comparingInt(p -> p.age);
java.util.Arrays.sort(arr, byAge);
int idx = java.util.Arrays.binarySearch(arr, new Person("X",25), byAge);
System.out.println(idx + ", " + arr[idx]);
1, B:20 // если компаратор сравнивает только по age, возвращаемый индекс указывает на элемент с совпадающим age
Обработка NaN и знакового нуля для double/float (пример с безопасным компаратором)
double[] a = {Double.NEGATIVE_INFINITY, -0.0, 0.0, Double.POSITIVE_INFINITY, Double.NaN};
// рекомендуемая практика: явно определять порядок NaN и -0.0 при необходимости
int idx1 = java.util.Arrays.binarySearch(a, 0.0);
int idx2 = java.util.Arrays.binarySearch(a, Double.NaN);
System.out.println(idx1 + ", " + idx2);
2, 4
Использование для многомерных структур через ключи
// поиск по первому элементу пары
int[][] pairs = {{1,100},{2,200},{4,400}};
int key = 3;
java.util.Comparator comp = (x,y) -> Integer.compare(x[0], y[0]);
java.util.Arrays.sort(pairs, comp);
int idx = java.util.Arrays.binarySearch(pairs, new int[]{key,0}, comp);
int insertion = (idx < 0) ? -idx - 1 : idx;
System.out.println(idx + ", insertion=" + insertion);
-3, insertion=2
Поиск и одновременная вставка (эффективность для больших массивов)
int[] a = {1,2,4,5};
int key = 3;
int r = java.util.Arrays.binarySearch(a, key);
int ip = (r >= 0) ? r : -r - 1;
int[] b = new int[a.length + 1];
System.arraycopy(a, 0, b, 0, ip);
b[ip] = key;
System.arraycopy(a, ip, b, ip+1, a.length - ip);
System.out.println(java.util.Arrays.toString(b));
[1, 2, 3, 4, 5]