# Куча Куча (heap) - это полное двоичное дерево, удовлетворяющее определенным условиям. Основных типов кучи два, как показано на рисунке ниже. - Минимальная куча (min heap): значение любого узла $\leq$ значения его дочерних узлов. - Максимальная куча (max heap): значение любого узла $\geq$ значения его дочерних узлов. ![Минимальная куча и максимальная куча](heap.assets/min_heap_and_max_heap.png) Куча, являясь частным случаем полного двоичного дерева, обладает следующими свойствами. - Узлы самого нижнего уровня заполняются слева, а все остальные уровни заполнены полностью. - Корневой узел двоичного дерева мы называем вершиной кучи, а самый правый узел нижнего уровня - основанием кучи. - Для максимальной (минимальной) кучи значение элемента на вершине, то есть у корневого узла, является максимальным (минимальным). ## Распространенные операции с кучей Нужно отметить, что многие языки программирования предоставляют не саму кучу, а очередь с приоритетом (priority queue) - абстрактную структуру данных, определяемую как очередь, в которой элементы извлекаются в соответствии с приоритетом. На практике **куча обычно используется для реализации очереди с приоритетом, а максимальная куча эквивалентна очереди с приоритетом, в которой элементы извлекаются по убыванию**. С точки зрения использования очередь с приоритетом и куча могут считаться эквивалентными структурами данных. Поэтому в этой книге мы не будем специально различать их и в дальнейшем будем единообразно называть кучей. Распространенные операции с кучей приведены в таблице ниже. Конкретные имена методов зависят от языка программирования.

Таблица   Эффективность операций с кучей

| Имя метода | Описание | Временная сложность | | ----------- | ------------------------------------------------ | ------------------- | | `push()` | Поместить элемент в кучу | $O(\log n)$ | | `pop()` | Извлечь элемент с вершины кучи | $O(\log n)$ | | `peek()` | Получить доступ к вершине кучи (для max / min кучи это соответственно максимум / минимум) | $O(1)$ | | `size()` | Получить число элементов в куче | $O(1)$ | | `isEmpty()` | Проверить, пуста ли куча | $O(1)$ | В реальных приложениях мы можем напрямую использовать классы кучи, предоставляемые языком программирования, или классы очереди с приоритетом. Подобно сортировкам "по возрастанию" и "по убыванию", мы можем переключаться между "минимальной кучей" и "максимальной кучей", изменяя `flag` или модифицируя `Comparator` . Код приведен ниже: === "Python" ```python title="heap.py" # Инициализация минимальной кучи min_heap, flag = [], 1 # Инициализация максимальной кучи max_heap, flag = [], -1 # Модуль heapq в Python по умолчанию реализует минимальную кучу # Если инвертировать знак элемента перед добавлением, то отношение порядка перевернется и так реализуется максимальная куча # В этом примере flag = 1 соответствует минимальной куче, а flag = -1 - максимальной # Добавление элементов в кучу heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) # Получение элемента на вершине кучи peek: int = flag * max_heap[0] # 5 # Извлечение элемента с вершины кучи # Извлеченные элементы образуют последовательность по убыванию val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 val = flag * heapq.heappop(max_heap) # 2 val = flag * heapq.heappop(max_heap) # 1 # Получение размера кучи size: int = len(max_heap) # Проверка, пуста ли куча is_empty: bool = not max_heap # Построение кучи из входного списка min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` === "C++" ```cpp title="heap.cpp" /* Инициализация кучи */ // Инициализация минимальной кучи priority_queue, greater> minHeap; // Инициализация максимальной кучи priority_queue, less> maxHeap; /* Добавление элементов в кучу */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); /* Получение элемента на вершине кучи */ int peek = maxHeap.top(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 maxHeap.pop(); // 2 maxHeap.pop(); // 1 /* Получение размера кучи */ int size = maxHeap.size(); /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.empty(); /* Построение кучи из входного списка */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` === "Java" ```java title="heap.java" /* Инициализация кучи */ // Инициализация минимальной кучи Queue minHeap = new PriorityQueue<>(); // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); /* Добавление элементов в кучу */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); /* Получение элемента на вершине кучи */ int peek = maxHeap.peek(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 peek = maxHeap.poll(); // 2 peek = maxHeap.poll(); // 1 /* Получение размера кучи */ int size = maxHeap.size(); /* Проверка, пуста ли куча */ boolean isEmpty = maxHeap.isEmpty(); /* Построение кучи из входного списка */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" /* Инициализация кучи */ // Инициализация минимальной кучи PriorityQueue minHeap = new(); // Инициализация максимальной кучи (достаточно изменить Comparer через lambda) PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); /* Добавление элементов в кучу */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); /* Получение элемента на вершине кучи */ int peek = maxHeap.Peek();//5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 peek = maxHeap.Dequeue(); // 2 peek = maxHeap.Dequeue(); // 1 /* Получение размера кучи */ int size = maxHeap.Count; /* Проверка, пуста ли куча */ bool isEmpty = maxHeap.Count == 0; /* Построение кучи из входного списка */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" // В Go можно построить целочисленную максимальную кучу, реализовав heap.Interface // Для реализации heap.Interface также нужно реализовать sort.Interface type intHeap []any // Метод Push из heap.Interface, реализует добавление элемента в кучу func (h *intHeap) Push(x any) { // Push и Pop используют pointer receiver // Потому что они не только изменяют содержимое среза, но и его длину *h = append(*h, x.(int)) } // Метод Pop из heap.Interface, реализует извлечение элемента с вершины кучи func (h *intHeap) Pop() any { // Извлекаемый элемент хранится в конце last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } // Метод Len из sort.Interface func (h *intHeap) Len() int { return len(*h) } // Метод Less из sort.Interface func (h *intHeap) Less(i, j int) bool { // Для минимальной кучи здесь нужно заменить сравнение на < return (*h)[i].(int) > (*h)[j].(int) } // Метод Swap из sort.Interface func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } // Top получает элемент на вершине кучи func (h *intHeap) Top() any { return (*h)[0] } /* Driver Code */ func TestHeap(t *testing.T) { /* Инициализация кучи */ // Инициализация максимальной кучи maxHeap := &intHeap{} heap.Init(maxHeap) /* Добавление элементов в кучу */ // Вызываем методы heap.Interface для добавления элементов heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) /* Получение элемента на вершине кучи */ top := maxHeap.Top() fmt.Printf("Элемент на вершине кучи: %d\n", top) /* Извлечение элемента с вершины кучи */ // Вызываем методы heap.Interface для удаления элементов heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 heap.Pop(maxHeap) // 2 heap.Pop(maxHeap) // 1 /* Получение размера кучи */ size := len(*maxHeap) fmt.Printf("Число элементов в куче: %d\n", size) /* Проверка, пуста ли куча */ isEmpty := len(*maxHeap) == 0 fmt.Printf("Пуста ли куча: %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" /* Инициализация кучи */ // Тип Heap в Swift поддерживает и max-heap, и min-heap, но требует swift-collections var heap = Heap() /* Добавление элементов в кучу */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) /* Получение элемента на вершине кучи */ var peek = heap.max()! /* Извлечение элемента с вершины кучи */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 peek = heap.removeMax() // 2 peek = heap.removeMax() // 1 /* Получение размера кучи */ let size = heap.count /* Проверка, пуста ли куча */ let isEmpty = heap.isEmpty /* Построение кучи из входного списка */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" // В JavaScript нет встроенного класса Heap ``` === "TS" ```typescript title="heap.ts" // В TypeScript нет встроенного класса Heap ``` === "Dart" ```dart title="heap.dart" // В Dart нет встроенного класса Heap ``` === "Rust" ```rust title="heap.rs" use std::collections::BinaryHeap; use std::cmp::Reverse; /* Инициализация кучи */ // Инициализация минимальной кучи let mut min_heap = BinaryHeap::>::new(); // Инициализация максимальной кучи let mut max_heap = BinaryHeap::new(); /* Добавление элементов в кучу */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); /* Получение элемента на вершине кучи */ let peek = max_heap.peek().unwrap(); // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 let peek = max_heap.pop().unwrap(); // 2 let peek = max_heap.pop().unwrap(); // 1 /* Получение размера кучи */ let size = max_heap.len(); /* Проверка, пуста ли куча */ let is_empty = max_heap.is_empty(); /* Построение кучи из входного списка */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" // В C нет встроенного класса Heap ``` === "Kotlin" ```kotlin title="heap.kt" /* Инициализация кучи */ // Инициализация минимальной кучи var minHeap = PriorityQueue() // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } /* Добавление элементов в кучу */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) /* Получение элемента на вершине кучи */ var peek = maxHeap.peek() // 5 /* Извлечение элемента с вершины кучи */ // Извлеченные элементы образуют последовательность по убыванию peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 peek = maxHeap.poll() // 2 peek = maxHeap.poll() // 1 /* Получение размера кучи */ val size = maxHeap.size /* Проверка, пуста ли куча */ val isEmpty = maxHeap.isEmpty() /* Построение кучи из входного списка */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" # В Ruby нет встроенного класса Heap ``` ??? pythontutor "Визуализация выполнения" https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20min_heap%2C%20flag%20%3D%20%5B%5D%2C%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20max_heap%2C%20flag%20%3D%20%5B%5D%2C%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%20heapq%20%D0%B2%20Python%20%D0%BF%D0%BE%20%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D1%82%D1%80%D0%B8%D1%86%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B0%20%D0%B8%20%D1%82%D0%B5%D0%BC%20%D1%81%D0%B0%D0%BC%D1%8B%D0%BC%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%92%20%D1%8D%D1%82%D0%BE%D0%BC%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D0%B5%20flag%20%3D%201%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D0%B5%2C%20%D0%B0%20flag%20%3D%20-1%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20max-%D0%BA%D1%83%D1%87%D0%B5%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%201%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%203%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%202%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%205%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20flag%20%2A%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D1%83%D1%8E%D1%82%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BE%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B3%D0%BE%20%D0%BA%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%BC%D1%83%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%92%D1%85%D0%BE%D0%B4%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D0%B8%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20min_heap%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false ## Реализация кучи Ниже реализуется максимальная куча. Чтобы преобразовать ее в минимальную кучу, достаточно инвертировать всю логику сравнений по величине, например заменить $\geq$ на $\leq$ . Заинтересованные читатели могут попробовать реализовать это самостоятельно. ### Хранение и представление кучи В разделе "Двоичные деревья" мы уже говорили, что полное двоичное дерево очень удобно представлять массивом. Поскольку куча как раз и является полным двоичным деревом, **для хранения кучи мы также будем использовать массив**. Когда двоичное дерево представляется массивом, элементы массива соответствуют значениям узлов, а индексы - положениям этих узлов в двоичном дереве. **Указатели на узлы реализуются через формулы отображения индексов**. Как показано на рисунке ниже, для заданного индекса $i$ индекс левого дочернего узла равен $2i + 1$ , правого дочернего узла - $2i + 2$ , а родительского узла - $(i - 1) / 2$ с округлением вниз. Если индекс выходит за допустимые границы, это означает пустой узел или отсутствие узла. ![Представление и хранение кучи](heap.assets/representation_of_heap.png) Мы можем инкапсулировать формулы отображения индексов в функции, чтобы потом было удобнее ими пользоваться: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` ### Доступ к элементу на вершине кучи Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` ### Добавление элемента в кучу Пусть дан элемент `val` . Сначала мы помещаем его в основание кучи. После добавления свойства кучи могут нарушиться, потому что `val` может оказаться больше, чем другие элементы в куче. **Поэтому необходимо восстановить порядок на пути от вставленного узла к корню** ; эта операция называется упорядочиванием кучи. Рассмотрим ситуацию, когда упорядочивание выполняется **снизу вверх**, начиная от только что вставленного узла. Как показано на рисунках ниже, мы сравниваем значение вставленного узла со значением его родителя; если вставленный узел больше, то меняем их местами. Затем продолжаем выполнять ту же операцию и последовательно восстанавливать корректность всех узлов по пути снизу вверх, пока не выйдем за корень или не встретим узел, для которого обмен не требуется. === "<1>" ![Шаги добавления элемента в кучу](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) === "<3>" ![heap_push_step3](heap.assets/heap_push_step3.png) === "<4>" ![heap_push_step4](heap.assets/heap_push_step4.png) === "<5>" ![heap_push_step5](heap.assets/heap_push_step5.png) === "<6>" ![heap_push_step6](heap.assets/heap_push_step6.png) === "<7>" ![heap_push_step7](heap.assets/heap_push_step7.png) === "<8>" ![heap_push_step8](heap.assets/heap_push_step8.png) === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) Пусть общее число узлов равно $n$ , тогда высота дерева равна $O(\log n)$ . Следовательно, максимальное число итераций операции упорядочивания кучи тоже не превышает $O(\log n)$ . Отсюда **временная сложность добавления элемента в кучу равна $O(\log n)$** . Код приведен ниже: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` ### Извлечение элемента с вершины кучи Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка. Если просто удалить первый элемент списка, то индексы всех узлов двоичного дерева изменятся, и это сильно затруднит последующее восстановление структуры при помощи упорядочивания кучи. Чтобы по возможности минимизировать изменение индексов элементов, мы используем следующий порядок действий. 1. Поменять местами элемент на вершине кучи и элемент у основания кучи, то есть поменять корневой узел с самым правым листовым узлом. 2. После обмена удалить основание кучи из списка. Стоит отметить, что, поскольку обмен уже выполнен, фактически удаляется исходный элемент вершины кучи. 3. Начиная от корневого узла, **выполнить упорядочивание кучи сверху вниз**. Как показано на рисунках ниже, **направление операции упорядочивания кучи сверху вниз противоположно операции упорядочивания кучи снизу вверх**. Мы сравниваем значение корневого узла со значениями двух дочерних узлов, выбираем больший дочерний узел и меняем его местами с корневым узлом. Затем циклически повторяем ту же операцию, пока не выйдем за листовой узел или не встретим узел, который уже не требует обмена. === "<1>" ![Шаги извлечения элемента с вершины кучи](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) === "<3>" ![heap_pop_step3](heap.assets/heap_pop_step3.png) === "<4>" ![heap_pop_step4](heap.assets/heap_pop_step4.png) === "<5>" ![heap_pop_step5](heap.assets/heap_pop_step5.png) === "<6>" ![heap_pop_step6](heap.assets/heap_pop_step6.png) === "<7>" ![heap_pop_step7](heap.assets/heap_pop_step7.png) === "<8>" ![heap_pop_step8](heap.assets/heap_pop_step8.png) === "<9>" ![heap_pop_step9](heap.assets/heap_pop_step9.png) === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) Как и операция добавления в кучу, операция извлечения элемента с вершины кучи также имеет временную сложность $O(\log n)$ . Код приведен ниже: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` ## Типичные применения кучи - **Очередь с приоритетом**: куча обычно является предпочтительной структурой данных для реализации очереди с приоритетом; добавление и извлечение элементов имеют временную сложность $O(\log n)$ , а построение кучи - $O(n)$ , и все эти операции выполняются очень эффективно. - **Пирамидальная сортировка**: для заданного набора данных можно построить кучу, а затем непрерывно извлекать из нее элементы, получая отсортированные данные. Однако на практике мы обычно используем более изящную реализацию пирамидальной сортировки; подробности см. в разделе "Пирамидальная сортировка". - **Получение наибольших $k$ элементов**: это классическая алгоритмическая задача и одновременно типичное применение кучи. Например, выбор 10 самых горячих новостей для списка популярных тем или выбор 10 самых продаваемых товаров.