1. Общ преглед
В тази статия ще разгледаме предимствата на бинарното търсене пред простото линейно търсене и ще разгледаме неговото внедряване в Java.
2. Нужда от ефективно търсене
Да приемем, че сме в бизнеса с продажба на вино и милиони купувачи посещават нашето приложение всеки ден.
Чрез нашето приложение клиент може да филтрира артикули с цена под n долара, да избере бутилка от резултатите от търсенето и да ги добави в количката си. Имаме милиони потребители, които търсят вина с ценова граница всяка секунда. Резултатите трябва да са бързи.
В бекенда нашият алгоритъм провежда линейно търсене в целия списък с вина, като сравнява ценовия лимит, въведен от клиента с цената на всяка бутилка вино в списъка.
След това връща артикули, чиято цена е по-малка или равна на ценовия лимит. Това линейно търсене има времева сложност на O (n) .
Това означава, че колкото по-голям е броят на бутилките за вино в нашата система, толкова повече време ще отнеме. Времето за търсене се увеличава пропорционално на броя на въведените нови елементи.
Ако започнем да запазваме елементи в сортиран ред и търсим елементи с помощта на двоичното търсене, можем да постигнем сложност на O (log n) .
При двоично търсене времето, необходимо на резултатите от търсенето, естествено се увеличава с размера на набора от данни, но не пропорционално.
3. Двоично търсене
Най-просто казано, алгоритъмът сравнява ключовата стойност със средния елемент на масива; ако те са неравностойни, половината, в която ключът не може да бъде част, се елиминира и търсенето продължава за останалата половина, докато успее.
Не забравяйте - ключовият аспект тук е, че масивът вече е сортиран.
Ако търсенето завърши с останалата половина празна, ключът не е в масива.
3.1. Итеративно Impl
public int runBinarySearchIteratively( int[] sortedArray, int key, int low, int high) { int index = Integer.MAX_VALUE; while (low <= high) { int mid = (low + high) / 2; if (sortedArray[mid] key) { high = mid - 1; } else if (sortedArray[mid] == key) { index = mid; break; } } return index; }
Методът runBinarySearchIteratively взема sortedArray , ключ & ниските и високите индекси на sortedArray като аргументи. Когато методът изпълнява за първи път най- ниското , първият индекс на sortedArray е 0, докато високият , последният индекс на sortedArray, е равен на дължината му - 1.
В средата е средният индекс на sortedArray . Сега алгоритъмът изпълнява цикъл while, сравнявайки ключа със стойността на масива на средния индекс на sortedArray .
3.2. Рекурсивно Impl
Сега, нека разгледаме и проста, рекурсивна реализация:
public int runBinarySearchRecursively( int[] sortedArray, int key, int low, int high) { int middle = (low + high) / 2; if (high < low) { return -1; } if (key == sortedArray[middle]) { return middle; } else if (key < sortedArray[middle]) { return runBinarySearchRecursively( sortedArray, key, low, middle - 1); } else { return runBinarySearchRecursively( sortedArray, key, middle + 1, high); } }
В runBinarySearchRecursively метод приема sortedArray , ключ, на ниски и високи показатели на sortedArray .
3.3. Използване на масиви. binarySearch ()
int index = Arrays.binarySearch(sortedArray, key);
SortedArray и ключ int , които трябва да се търсят в масива от цели числа, се предават като аргументи на метода binarySearch на класа Java Arrays .
3.4. Използване на колекции. binarySearch ()
int index = Collections.binarySearch(sortedList, key);
А sortedList и на Integer ключ , който трябва да се търси в списъка на Integer обекти, се предават като аргументи за binarySearch метод на Java Колекции клас.
3.5. производителност
Дали да се използва рекурсивен или итеративен подход за писане на алгоритъма е най-вече въпрос на лични предпочитания. Но все пак има няколко точки, с които трябва да сме наясно:
1. Рекурсията може да бъде по-бавна поради режийните разходи за поддържане на стека и обикновено заема повече памет
2. Рекурсията не е удобна за стека . Това може да причини StackOverflowException при обработка на масиви от големи данни
3. Рекурсията добавя яснота към кода, тъй като го прави по-кратък в сравнение с итеративния подход
В идеалния случай бинарното търсене ще извърши по-малък брой сравнения за разлика от линейното търсене на големи стойности на n. При по-малки стойности на n линейното търсене може да се представи по-добре от бинарното търсене.
Трябва да се знае, че този анализ е теоретичен и може да варира в зависимост от контекста.
Също така, двоичният алгоритъм за търсене се нуждае от сортиран набор от данни, който също има своите разходи . Ако използваме алгоритъм за сортиране на сливане за сортиране на данните, към нашия код се добавя допълнителна сложност от n log n .
Така че първо трябва да анализираме добре нашите изисквания и след това да вземем решение кой алгоритъм за търсене ще отговаря най-добре на нашите изисквания.
4. Заключение
Този урок демонстрира изпълнение на двоичен алгоритъм за търсене и сценарий, при който би било за предпочитане да се използва вместо линейно търсене.
Моля, намерете кода за урока на GitHub.