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

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

Heap: чтение кода

Суть Чтение реального кода heap и предсказание поведения: баг sift-up, граница цикла build-heap, top-k через heap размера k и аргумент сложности heapify.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Баги в heap прячутся в индексной арифметике и границах циклов — перевёрнутый знак, off-by-one в последнем нелистовом узле, путаница min/max. Читайте каждый фрагмент как на ревью: проследите инвариант, затем выберите точный дефект или верное утверждение.

Цель

Потренируйтесь читать код heap на уровне ревью сеньора: заметить sift-up со сравнением не в ту сторону, обосновать границу цикла build-heap, подтвердить, что heap для top-k правильного размера и ориентации, и защитить, почему heapify есть O(n).

Фрагмент 1 — sift-up

// Замысел: min-heap. siftUp восстанавливает свойство после push.
function siftUp(heap, i) {
  while (i > 0) {
    const parent = Math.floor((i - 1) / 2);
    if (heap[i] > heap[parent]) {        // <-- сравнение
      [heap[i], heap[parent]] = [heap[parent], heap[i]];
      i = parent;
    } else {
      break;
    }
  }
}
Викторина

Это должно поддерживать MIN-heap, но после push свойство нарушается. В чём дефект?

Фрагмент 2 — build-heap

function buildHeap(arr) {           // sift-down каждого нелистового узла снизу вверх
  const start = Math.floor(arr.length / 2) - 1;
  for (let i = start; i >= 0; i--) {
    siftDown(arr, i);
  }
  return arr;
}
Викторина

Почему цикл стартует с Math.floor(n/2) - 1 и идёт вниз, и что сломается, если стартовать с 0 и идти вверх?

Фрагмент 3 — top-k

function topKLargest(nums, k) {
  const h = new MinHeap();              // min-heap, корень — наименьший
  for (const x of nums) {
    h.push(x);
    if (h.size() > k) h.pop();          // выбрасываем текущий минимум
  }
  return h.toArray();                   // k наибольших (в порядке heap)
}
Викторина

Для nums = [4, 1, 7, 3, 8, 5] и k = 3 что держит heap в конце и каково время работы?

Фрагмент 4 — сложность heapify

Heap, построенный снизу вверх, вызывает sift-down по разу на нелистовой узел.
Число узлов по высоте h (всего n):  ~n/2 при h=0, n/4 при h=1, n/8 при h=2, ...
стоимость sift-down из узла высоты h есть O(h).
Полная работа  T(n) = Σ_h (n / 2^(h+1)) · h
Викторина

Вычисляя сумму T(n) = Σ_h (n / 2^(h+1)) · h, какова сложность build-heap и почему интуиция «n/2 sift-down × O(log n) каждый = O(n log n)» ошибочна?

Итог

Корректность heap живёт в деталях, которые читаешь построчно: сравнение в sift-up должно соответствовать min или max (ребёнок < родитель для min-heap); build-heap должен идти снизу вверх от последнего нелистового на floor(n/2)-1, чтобы поддеревья каждого узла уже были heap; top-k использует MIN-heap размера k и выбрасывает корень при переполнении, оставляя k наибольших за O(n log k); а heapify есть O(n), потому что взвешенная по высотам сумма узлов Σ h/2^h сходится — большинство узлов это дешёвые листья. Проследите инвариант, прежде чем довериться коду.

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

Trademarks belong to their respective owners. Editorial reference only.