Skip to main content

Двоичный поиск

Описание

Двоичный (бинарный) поиск (метод деления пополам или дихотомия) (binary/half-interval/logarithmic search, binary chop) -это классический алгоритм поиска элемента в отсортированном массиве (векторе), использующий дробление массива на половины. Суть такая: мы сравниваем целевое значение с центральным элементом массива. Если они неравны, половина, в которой точно нет целевого значения, отбрасывается, и поиск продолжается в другой половине по такому же принципу. Если поиск завершился пустым массивом, значит, целевое значение в массиве отсутствует.


Временная сложность алгоритма составляет O(log(n)), поскольку мы уменьшаем площадь поиска вдвое на каждой итерации.

Реализация

// algorithms/searches/binary-search.js
import Comparator from '../../utils/comparator'

export default function binarySearch(sortedArr, target, fn) {
const comparator = new Comparator(fn)

let start = 0
let end = sortedArr.length - 1

while (start <= end) {
// Вычисляем индекс центрального элемента
let middle = start + Math.floor((end - start) / 2)

if (comparator.equal(sortedArr[middle], target)) {
return middle
}

// Если целевое значение меньше центрального элемента
if (comparator.lessThan(sortedArr[middle], target)) {
// Переходим к правой половине массива
start = middle + 1
} else {
// Переходим к левой половине массива
end = middle - 1
}
}

return -1
}

Тестирование

// algorithms/searches/__tests__/binary-search.test.js
import binarySearch from '../binary-search'

describe('binarySearch', () => {
it('должен найти числа в отсортированных массивах', () => {
expect(binarySearch([], 1)).toBe(-1)
expect(binarySearch([1], 1)).toBe(0)
expect(binarySearch([1, 2], 1)).toBe(0)
expect(binarySearch([1, 2], 2)).toBe(1)
expect(binarySearch([1, 5, 10, 12], 1)).toBe(0)
expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 17)).toBe(5)
expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 1)).toBe(0)
expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 100)).toBe(7)
expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 0)).toBe(-1)
})

it('должен найти объекты в отсортированном массиве', () => {
const sortedArrayOfObjects = [
{ key: 1, value: 'value1' },
{ key: 2, value: 'value2' },
{ key: 3, value: 'value3' },
]

const comparator = (a, b) => {
if (a.key === b.key) return 0
return a.key < b.key ? -1 : 1
}

expect(binarySearch([], { key: 1 }, comparator)).toBe(-1)
expect(binarySearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1)
expect(binarySearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0)
expect(binarySearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1)
expect(binarySearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2)
})
})

Запускаем тестирование:

npm run test ./algorithms/searches/__tests__/binary-search

Результат:


Такие варианты двоичного поиска предлагают разработчики VSCode:

// https://github.com/microsoft/vscode/blob/main/src/vs/base/common/arrays.ts#L73
export function binarySearch(array, key, comparator) {
return binarySearch2(array.length, i => comparator(array[i], key));
}

export function binarySearch2(length, compareToKey) {
let low = 0,
high = length - 1;

while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = compareToKey(mid);
if (comp < 0) {
low = mid + 1;
} else if (comp > 0) {
high = mid - 1;
} else {
return mid;
}
}
return -(low + 1);
}

// Тоже самое, но в форме одной функции
// https://github.com/microsoft/vscode/blob/main/extensions/html-language-features/server/src/utils/arrays.ts#L60
export function binarySearch(array, key, comparator) {
let low = 0,
high = array.length - 1;

while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = comparator(array[mid], key);
if (comp < 0) {
low = mid + 1;
} else if (comp > 0) {
high = mid - 1;
} else {
return mid;
}
}
return -(low + 1);
}