Алгоритм пирамидальной сортировки (Heap Sort): принцип работы

Модуль "Сортировки" • урок 7 из 17 •

Также упоминается как: Сортировка кучей, Хипсорт, Пирамидальная сортировка с просеиванием

Суть урока. Пирамидальная сортировка (Heap Sort): строит из массива сортирующее дерево (кучу), где корень всегда содержит максимум. Меняет максимум с концом и просеивает новый корень вниз. Гарантированно O(N log N) без дополнительной памяти O(1).

Пирамидальная сортировка - это великолепный алгоритм сложности O(N log N), который, в отличие от турнирной сортировки или сортировки слиянием, вообще не требует дополнительной памяти. Сортировка происходит "на месте" внутри самого массива.

Идея метода сортировки

Алгоритм рассматривает обычный массив как структуру "Двоичная куча" (Max-Heap). В куче значение каждого узла больше либо равно значениям его потомков. Корень кучи всегда лежит в arr[0], а дети узла i лежат по индексам 2*i + 1 и 2*i + 2.

  1. Построение кучи: Идем от середины массива к началу, выполняя для каждого элемента операцию Просеивание (SiftDown). Массив превращается в max-кучу, и абсолютный максимум оказывается в индексе 0.
  2. Сортировка: Берем arr[0] (максимум) и меняем его с последним элементом массива. Теперь максимум стоит на своем законном месте!
  3. Размер "кучи" условно уменьшаем на 1. Новый arr[0] нарушает свойство пирамиды, поэтому мы "просеиваем" его вниз, пока он не найдет свое место. Следующий максимум всплывает на вершину. Повторяем процесс, пока куча не опустеет.
Алгоритм пирамидальной сортировки: отображение двоичной кучи в массив
Пирамидальная сортировка: взаимно-однозначное отображение двоичной кучи в одномерный массив с формулами вычисления потомков.

Ниже можно в динамике изучить механику пирамидальной сортировки: от построения двоичной кучи до поочередного просеивания элементов.

Тренажер: алгоритм пирамидальной сортировки

Отсортируем 3 1 6 5 2 7 4, глядя на массив как на дерево.

узел кучи обмен (siftDown) на финальном месте
Отсортировано: 0 / 7 Обменов: 0 Шаг: 0 / 0
/\
Heap Sort
Пирамидальная сортировка
Массив как двоичное дерево: строим кучу, забираем максимум из корня и просеиваем новый. O(n log n) на любых данных.
Разобрать по шагам в большой версии →

Реализация на C

Операция SiftDown (Просеивание)

void Swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }

// Просеивание элемента вниз по пирамиде
void SiftDown(int arr[], int n, int i) {
    int largest = i;          // Инициализируем корень как наибольший
    int left = 2 * i + 1;     // Левый потомок
    int right = 2 * i + 2;    // Правый потомок

    // Если левый ребенок больше корня
    if (left < n && arr[left] > arr[largest]) largest = left;
    
    // Если правый ребенок больше текущего максимума
    if (right < n && arr[right] > arr[largest]) largest = right;

    // Если максимум не в корне
    if (largest != i) {
        Swap(&arr[i], &arr[largest]);
        // Рекурсивно просеиваем дальше вниз (можно сделать циклом без рекурсии)
        SiftDown(arr, n, largest);
    }
}

Сам алгоритм пирамидальной сортировки

void HeapSort(int arr[], int n) {
    // 1. Построение кучи (начинаем с последнего узла, у которого есть дети: n/2 - 1)
    for (int i = n / 2 - 1; i >= 0; i--) {
        SiftDown(arr, n, i);
    }

    // 2. Сортировка: последовательно извлекаем максимум
    for (int i = n - 1; i >= 0; i--) {
        Swap(&arr[0], &arr[i]); // Максимум в конец
        SiftDown(arr, i, 0);   // Просеиваем новый корень в куче размера 'i'
    }
}
А как в Python?
Работа с двоичной кучей встроена в Python через модуль heapq. Однако стандартный list.sort() в Питоне пирамидальную сортировку не использует. Причина в том, что пирамидальная сортировка неустойчива и выполняет "скачки" по далеким адресам памяти, что приводит к постоянным промахам процессорного кэша. Поэтому Timsort работает на реальном железе быстрее, несмотря на одинаковую теоретическую сложность O(N log N).

Коротко: главное

  • Пирамидальная сортировка гарантированно работает за время O(N log N).
  • Сортировка происходит "на месте", поэтому дополнительная память O(1) не требуется.
  • Алгоритм строится на структуре "двоичная куча", логически размещенной прямо в массиве.
  • На каждом шаге максимальный элемент из корня перемещается в конец, а новый корень просеивается вниз.
  • Алгоритм является неустойчивым и не используется по умолчанию в Python из-за частых промахов процессорного кэша.

Проверьте себя

В чем главное преимущество алгоритма пирамидальной сортировки по сравнению с турнирной сортировкой?

Как вам урок? Спасибо за отзыв!