4.5. Демонстрация - Рекурсивный бинарный поиск

Шаг 1

Чуть упростим задачу поиска. Теперь будем не возвращать индекс искомого элемента, а лишь говорить, присутствует элемент или нет (этакий оптимизированный includes на сортированном массиве). Попробуйте самостоятельно переписать бинарный поиск по новым условиям и с помощью рекурсии.


import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  return false;
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

Шаг 2

Очевидно, что базовый случай у этой задачи — поиск в массиве из одного элемента. Если он является нашим искомым элементом — возвращаем true, иначе — false. Если массив пустой, искать тоже не стоит — просто вернём false.


import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  if (array.length === 0) {
    return false;
  }

  if (array.length === 1) {
    return array[0] === element;
  }

  return false;
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

Шаг 3

А теперь начинаем выделять из этой задачи подзадачи — делим наш массив пополам.


import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  if (array.length === 0) {
    return false;
  }

  if (array.length === 1) {
    return array[0] === element;
  }

  const middle = Math.floor(array.length / 2);

  return false;
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

Шаг 4

И запускаем подзадачу поиска в нужной нам части массива! Определять нужную часть массива мы уже научились в прошлой главе.


  import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  if (array.length === 0) {
    return false;
  }

  if (array.length === 1) {
    return array[0] === element;
  }

  const middle = Math.floor(array.length / 2);

  if (array[middle] > element) {
    return binarySearch(array.slice(0, middle), element)
  }

  return binarySearch(array.slice(middle + 1), element)
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

Шаг 5

На самом деле, мы потеряли ещё один базовый случай: если в середине уже лежит искомый нам элемент, нам не нужно создавать подзадачи, просто будем возвращать true.


  import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  if (array.length === 0) {
    return false;
  }

  if (array.length === 1) {
    return array[0] === element;
  }

  const middle = Math.floor(array.length / 2);

  if (array[middle] === element) {
    return true;
  }

  if (array[middle] > element) {
    return binarySearch(array.slice(0, middle), element)
  }

  return binarySearch(array.slice(middle + 1), element)
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

Шаг 6

Здесь довольно хорошо видно, что рекурсивные решения проще для понимания относительно итеративных. Вместо манипуляций с указателями мы просто запускаем поиск по нужной части внутри основной задачи. Использование рекурсии и подхода «Разделяй и властвуй» — зачастую не единственный, но самый выразительный способ понятно донести свои мысли в код.


 import {test} from './tester';

/*
Примеры:
binarySearch([], 3) // false
binarySearch([3], 3) // true
binarySearch([1, 2, 3, 4, 5], 4) // true
binarySearch([1, 2, 3, 5, 6], 4) // false
*/

function binarySearch(array, element) {
  if (array.length === 0) {
    return false;
  }

  if (array.length === 1) {
    return array[0] === element;
  }

  const middle = Math.floor(array.length / 2);

  if (array[middle] === element) {
    return true;
  }

  if (array[middle] > element) {
    return binarySearch(array.slice(0, middle), element)
  }

  return binarySearch(array.slice(middle + 1), element)
}

test(binarySearch([], 3), false);
test(binarySearch([3], 3), true);
test(binarySearch([1, 2, 3, 4, 5], 4), true);
test(binarySearch([1, 2, 3, 5, 6], 4), false);

File tester.js


const results = document.getElementById('results')

export function test(left, right) {
  const result = document.createElement('li');

  if (left === right) {
    result.innerHTML = "Тест пройден!";
    result.style.color = "green";
  } else {
    result.innerHTML = `Тест не пройден! ${left} не равно ${right}`;
    result.style.color = "red";
  }

  results.appendChild(result);
}