Целочисленный двоичный поиск

Пусть мы хотим найти в отсортированном массиве длины .

Применим концепцию из задачи "угадай число от до ".

Более формально. Используем идею метода "разделяй и властвуй" — посмотрим на элемент в середине массива . Если , то мы нашли . Если , то может находиться среди элементов правее — , так как все элементы левее -го меньше (массив отсортирован). Если , аналогичными рассуждением получаем, что ответ стоит искать в .

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

Получается алгоритм использует "разделяй и властвуй" (на задаче на отрезке переходит на более малый отрезок), на каждой итерации он сравнивает с центральным элементов на отрезке и в случае неудачи сокращает длину отрезка в два раза. Итерации алгоритма совершаются пока мы не найдём или длина отрезка станет равна .

Поскольку на каждом шаге длина отрезка уменьшается вдвое, это произойдёт через шагов.

Примерная реализация алгоритма двоичного поиска:

Функция binarySearch ищет в массиве длины , возвращает индекс, по которому лежит , или , если в массиве нет.

int binarySearch(int* a, int n, int x) {
  int l = 0, r = n - 1;
  while (l <= r) {
    int mid = (l + r) / 2;  // индекс элемента в центре отрезка [l, r]
    if (a[mid] == x) {
      return mid;
    } else if (a[mid] < x) {
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  return -1;
}

На самом деле проще писать бинпоиск лишь с одним условием внутри. Подробнее написано в следующей главе.

int binarySearch(int* a, int n, int x) {
  int l = 0, r = n - 1;
  while (r - l > 1) {
    int mid = (l + r) / 2;
    if (a[mid] < x) {
      l = mid;
    } else {
      r = mid;
    }
  }
  return a[r] == x || a[l] == x;
}

Проверка на a[l] = x нужна лишь если a[0] = x, так как в таком случае после поиска l будет равна 0.

Количество вхождений в массив

Дан массив и вопросов. Каждый запрос задан числом . Для каждого запроса определите количество вхождений в массив .

Решение : Отсортируем массив . Для каждого с помощью бинпоиска найдём количество вхождений.

Для количества вхождений в отсортированном массиве , достаточно получить индексы самого левого и самого правого вхождений в массив. Тогда количество вхождений — это разность этих индексов плюс один.

Сделаем два различных бинпоиска, для поиска l и r. Первый вернёт минимальное такое, что или если все элементы меньше . Второй найдёт максимальное такое, что или , если все элементы не больше . В данном случае ответ будет разница этих индексов. В случае, если нет в массиве то результат будет , так как . В обычном мире первая функция называется lower_bound, а вторая lower_bound. Подробнее почитать про них в языке c++ можно тут и тут, а тут про их различие.

Last change: 2023-10-16, commit: 0f1c938