2.3.3.1 — дерево без единого указателя, целиком закодированное индексами массива2.3.3.1 — a tree with not a single pointer, entirely encoded via array indices

Бинарная КучаBinary Heap

Бинарная куча — полное бинарное дерево (все уровни заполнены, кроме, возможно, последнего, который заполняется слева направо), хранящееся в обычном массиве без единого узла-объекта и указателя: у элемента с индексом i дети — 2i+1 и 2i+2, родитель — (i-1)/2. Инвариант — heap-order: ключ родителя ≤ ключей обоих детей (min-куча). Это структура данных под heapsort (2.2.1, где дерево пересобирается in-place ради сортировки массива целиком), но здесь она рассматривается как динамическая очередь с приоритетом: вставка и извлечение минимума, а не одноразовая сортировка.A binary heap is a complete binary tree (every level fully filled except possibly the last, which fills left to right), stored in a plain array with not a single node object or pointer: the element at index i has children at 2i+1 and 2i+2, and a parent at (i-1)/2. The invariant is heap-order: a parent's key is ≤ both children's keys (a min-heap). This is the data structure behind heapsort (2.2.1, where the tree is rebuilt in-place to sort an entire array), but here it's treated as a dynamic priority queue: insertion and extracting the minimum, rather than a one-shot sort.

insert/extract-min O(log n) peek-min O(1) merge — O(n)merge — O(n)

Идея и инвариантIdea & invariant

Полнота дерева (все уровни, кроме последнего, полностью заполнены, последний заполняется слева направо без пропусков) — это то, что позволяет хранить дерево в массиве без указателей: позиция каждого узла однозначно определяется его индексом. Heap-order — единственный инвариант: он гарантирует, что корень (индекс 0) — минимум всего дерева, но ничего не говорит о порядке между произвольными двумя узлами, не находящимися в отношении предок-потомок.Completeness of the tree (every level except the last is fully filled, and the last fills left to right without gaps) is what lets the tree live in an array without pointers: each node's position is uniquely determined by its index. Heap-order is the only invariant: it guarantees the root (index 0) is the minimum of the whole tree, but says nothing about the order between any two nodes that aren't in an ancestor-descendant relationship.

Вставка (insert): новый элемент дописывается в конец массива (сохраняя полноту дерева), затем "всплывает" вверх (sift-up / bubble-up) — сравнивается с родителем, и если нарушает инвариант, меняется с ним местами; повторяется, пока инвариант не восстановлен или элемент не достиг корня. Путь всплытия — это путь от листа до корня, то есть не более высоты дерева шагов: O(log n). Извлечение минимума (extract-min): корень — искомый минимум; на его место переносится последний элемент массива (который затем удаляется), и новый корень "тонет" вниз (sift-down / bubble-down) — сравнивается с обоими детьми, меняется местами с меньшим из них, если инвариант нарушен, и так далее до листа или до восстановления инварианта. Тоже O(log n). Peek-min — это просто чтение a[0], O(1).Insertion: a new element is appended to the end of the array (preserving completeness), then "sifts up" (bubble-up) — it's compared with its parent, and swapped with it if the invariant is violated; this repeats until the invariant holds or the element reaches the root. The sift-up path is a path from a leaf to the root, i.e. at most the tree's height in steps: O(log n). Extracting the minimum: the root is the answer; the array's last element is moved into its place (and then removed), and the new root "sifts down" — it's compared with both children, swapped with the smaller of the two if the invariant is violated, and so on down to a leaf or until the invariant holds. Also O(log n). Peek-min is just reading a[0], O(1).

дерево [1,3,2,7,4,5] и его же представление массивом 1 3 2 7 4 5 1 3 2 7 4 5 i: 0 1 2 3 4 5

дети индекса i — это 2i+1 и 2i+2, родитель — (i-1)/2 (целочисленно); никакого явного дерева не хранитсяindex i's children are 2i+1 and 2i+2, its parent is (i-1)/2 (integer division); no explicit tree is stored at all

struct BinaryHeap {
    std::vector<int> a;

    int parent(int i) { return (i - 1) / 2; }
    int left(int i)   { return 2 * i + 1; }
    int right(int i)  { return 2 * i + 2; }

    void push(int key) {
        a.push_back(key);
        int i = a.size() - 1;
        while (i > 0 && a[parent(i)] > a[i]) {   // sift-up// sift-up
            std::swap(a[parent(i)], a[i]);
            i = parent(i);
        }
    }

    int pop_min() {
        int top = a[0];
        a[0] = a.back();
        a.pop_back();
        int i = 0, n = a.size();
        while (true) {                        // sift-down// sift-down
            int smallest = i, l = left(i), r = right(i);
            if (l < n && a[l] < a[smallest]) smallest = l;
            if (r < n && a[r] < a[smallest]) smallest = r;
            if (smallest == i) break;
            std::swap(a[i], a[smallest]);
            i = smallest;
        }
        return top;
    }
};

Демонстрация вживуюLive demonstration

insert проигрывает sift-up шаг за шагом — оранжевая рамка отмечает текущий сравниваемый узел, зелёная стрелка — обмен с родителем. extract-min переносит последний элемент в корень и проигрывает sift-down теми же цветами.insert auto-plays sift-up step by step — the orange outline marks the node currently being compared, the swap with its parent is highlighted. extract-min moves the last element to the root and auto-plays sift-down with the same colors.

выберите операцию — sift-up/sift-down проигрываются автоматическиpick an operation — sift-up/sift-down auto-play

Почему heapsort уже разобран отдельно, а здесь — другая рольWhy heapsort was already covered elsewhere, and this article's role is different

Пирамидальная сортировка (heapsort, 2.2.1) использует ровно эту же структуру, но в другом режиме: строит кучу за один проход (build-heap, O(n), а не n вставок за O(n log n)) и затем n раз извлекает максимум, каждый раз кладя его в конец массива, — весь процесс происходит in-place в одном и том же массиве, который в итоге становится отсортированным. Здесь же куча рассматривается как динамическая структура данных под очередь с приоритетом: элементы добавляются и извлекаются в произвольном порядке и в произвольные моменты времени, а не в рамках одного алгоритма сортировки конечного массива.Heapsort (2.2.1) uses this exact same structure but in a different mode: it builds the heap in a single pass (build-heap, O(n) rather than n insertions at O(n log n)) and then extracts the maximum n times, each time placing it at the end of the array — the whole process happens in-place in the same array, which ends up sorted. Here, the heap is treated as a dynamic data structure for a priority queue instead: elements are added and removed in arbitrary order and at arbitrary points in time, not as part of a single algorithm sorting one finite array.

Где встречаетсяWhere it shows up

01

std::priority_queue, heapq, java.util.PriorityQueue

Все три — прямая реализация бинарной кучи поверх динамического массива (std::vector, обычный список Python, ArrayList-подобная структура в Java). Никакого дерева объектов, никаких указателей — только массив и функции индексной арифметики.All three are a direct implementation of a binary heap on top of a dynamic array (std::vector, a plain Python list, an ArrayList-like structure in Java). No tree of objects, no pointers — just an array and index-arithmetic functions.

02

Планировщики задач с приоритетом и очереди готовностиPriority task schedulers and ready queues

Многие планировщики задач с явным приоритетом (не time-slicing, а именно "выполнить сначала самое важное") держат очередь ожидающих задач в бинарной куче, ключ — приоритет или дедлайн: extract-min/max выдаёт следующую задачу за O(log n) без полной пересортировки списка при каждом добавлении.Many task schedulers with an explicit priority (not time-slicing, but "run the most important thing first") keep their queue of pending tasks in a binary heap keyed by priority or deadline: extract-min/max yields the next task in O(log n) without fully re-sorting the list on every addition.