awesome-everything EN
↑ Обратно к восхождению

Алгоритмы с нуля

Сортировка и поиск: чтение кода

Суть Читаем реальные сниппеты сортировки и поиска — ошибки на единицу в binary search, partition Lomuto, stable-слияние и quickselect — предсказываем баг или поведение и выбираем фикс.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Баги сортировки и поиска прячутся на границах: не то сравнение, половина, отброшенная на индекс дальше нужного, swap, теряющий stable-свойство. Читайте каждый сниппет как на ревью и называйте дефект — или точное поведение — прежде чем выбрать.

Цель

Отработайте цикл ревьюера для этого юнита: проследите инвариант в голове, найдите ошибку на единицу или потерянную гарантию и выберите минимальный корректный фикс.

Сниппет 1 — бесконечный цикл

function bsearch(arr, target) {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = Math.floor((lo + hi) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) lo = mid;   // отбрасываем левую часть
    else hi = mid - 1;                 // отбрасываем правую часть
  }
  return -1;
}
Викторина

На многих входах это зависает навсегда. В чём баг и каков фикс?

Сниппет 2 — переполнение в вычислении середины

int bsearch(int arr[], int n, int target) {
    int lo = 0, hi = n - 1;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;        // <-- здесь
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}
Викторина

Это проходило все маленькие тесты, но портило результаты на многогигабайтном отсортированном массиве из 32-битных int. Почему и каков канонический фикс?

Сниппет 3 — partition Lomuto

function partition(arr, low, high) {
  const pivot = arr[high];
  let i = low - 1;
  for (let j = low; j < high; j++) {
    if (arr[j] < pivot) {
      i++;
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
  }
  [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
  return i + 1;
}
Викторина

Что гарантирует возвращаемый индекс и почему эта схема делает quicksort не stable?

Сниппет 4 — quickselect для k-го наименьшего

function quickselect(arr, k, lo = 0, hi = arr.length - 1) {
  const p = partition(arr, lo, hi);    // финальный индекс pivot
  if (p === k) return arr[p];
  if (p < k) return quickselect(arr, k, p + 1, hi);
  return quickselect(arr, k, lo, p - 1);
}
Викторина

Quickselect находит k-й наименьший элемент, переиспользуя partition. Какова его сложность и почему это лучше полной сортировки для этой задачи?

Итог

Каждый дефект здесь живёт на границе: binary search должен сужать диапазон (lo = mid + 1, никогда lo = mid) и считать mid = lo + (hi - lo) / 2, чтобы обойти переполнение; partition Lomuto возвращает финальную отсортированную позицию pivot и не stable, потому что swap’ы пересекают равные ключи; а quickselect переиспользует этот partition, находя k-й элемент за O(n) в среднем рекурсией только в одну сторону. Сначала читайте инвариант — тогда фикс обычно в один токен.

Продолжить восхождение ↑Сортировка и поиск: движок запросов
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.