mirror of
https://github.com/krahets/hello-algo.git
synced 2026-05-05 10:13:06 +08:00
438 lines
36 KiB
Markdown
438 lines
36 KiB
Markdown
# Куча
|
||
|
||
{width="3.8072911198600177in" height="4.927083333333333in"}
|
||
|
||
1. **куча**
|
||
|
||
> Куча -- это полное двоичное дерево, удовлетворяющее определенным услови- ям, и делится на два основных типа, как показано на рис. 8.1.
|
||
|
||
- **Минимальная куча**: значение любого узла ≤ значений его дочерних узлов.
|
||
|
||
- **Максимальная куча**: значение любого узла ≥ значений его дочерних узлов.
|
||
|
||
> Является совершенным двоичным деревом, и значение любого узла **≤** значения его дочерних узлов
|
||
>
|
||
> Является совершенным двоичным деревом, и значение любого узла **≥** значения его дочерних узлов
|
||
|
||
**Минимальная куча Максимальная куча**
|
||
|
||
> **Рис. 8.1.** Минимальная и максимальная кучи
|
||
>
|
||
> Куча, как частный случай полного двоичного дерева, обладает следующими свойствами:
|
||
|
||
- узлы на самом нижнем уровне заполняются слева, остальные уровни полностью заполнены;
|
||
|
||
- корневой узел двоичного дерева называется вершиной кучи, а самый правый узел на нижнем уровне -- основанием кучи;
|
||
|
||
- для максимальной (минимальной) кучи значение элемента на вершине (т. е. корневом узле) является наибольшим (наименьшим).
|
||
|
||
### Основные операции с кучей
|
||
|
||
> Следует отметить, что многие языки программирования содержат приоритет- ную очередь, которая является абстрактной структурой данных, определяемой как очередь с приоритетной сортировкой.
|
||
>
|
||
> На практике **куча часто используется для реализации приоритетной очереди**, **где максимальная куча соответствует приоритетной очереди**, **из которой элементы извлекаются в порядке убывания**. С точки зрения использования приоритетную очередь и кучу можно считать эквивалентными структурами данных. Поэтому в данной книге они не различаются и называ- ются просто кучей.
|
||
>
|
||
> Основные операции с кучей представлены в табл. 8.1, названия методов в разных языках программирования могут отличаться.
|
||
>
|
||
> **Таблица 8.1.** Эффективность операций с кучей
|
||
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
| > **Метод** | > **Описание** | > **Временная сложность** |
|
||
+=============+===========================================================+===========================+
|
||
| > push() | > Вставка элемента в кучу | > *O*(log *n*) |
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
| > pop() | > Извлечение элемента с вершины кучи | > *O*(log *n*) |
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
| > peek() | > Доступ к элементу на вершине кучи (макс./мин. значение) | > *O*(1) |
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
| > size() | > Получение количества элементов в куче | > *O*(1) |
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
| > isEmpty() | > Проверка кучи на пустоту | > *O*(1) |
|
||
+-------------+-----------------------------------------------------------+---------------------------+
|
||
|
||
> В реальных приложениях можно напрямую использовать классы кучи (или приоритетной очереди), предоставляемые языком программирования.
|
||
>
|
||
> Подобно сортировочным алгоритмам по возрастанию и по убыванию, мож- но установить флаг или изменить компаратор для преобразования минималь- ной кучи в максимальную» и наоборот. Ниже приведен пример кода.
|
||
>
|
||
> \# === File: 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)
|
||
|
||
### Реализация кучи
|
||
|
||
> Ниже приведена реализация максимальной кучи. Для преобразования в ми- нимальную кучу достаточно инвертировать все логические сравнения (напри- мер, заменить ≥ на ≤). Заинтересованные читатели могут реализовать это са- мостоятельно.
|
||
|
||
##### Хранение и представление кучи
|
||
|
||
> В разделе «Двоичные деревья» упоминалось, что полные двоичные деревья удобно представлять в виде массива. **Поскольку куча является таким дере- вом**, **для ее хранения будем использовать массив**.
|
||
>
|
||
> При использовании массива для представления двоичного дерева элементы представляют значения узлов, а индексы -- их положение в дереве. **Указатели на узлы реализуются через формулы индексации**.
|
||
>
|
||
> Как показано на рис. 8.2, для заданного индекса массива *i* индекс левого до- чернего узла равен 2*i* + 1, правого -- 2*i* + 2, а индекс родительского узла -- (*i* -- 1) / 2 (целочисленное деление вниз). Выход за пределы индексации обозначает пустой узел или его отсутствие.
|
||
|
||

|
||
|
||
> **Рис. 8.2.** Представление и хранение кучи
|
||
>
|
||
> Формулы индексации можно для удобства использования обернуть в функции.
|
||
>
|
||
> \# === File: my_heap.py ===
|
||
>
|
||
> def left(self, i: int) -\> int:
|
||
>
|
||
> \"\"\" Получение индекса левого дочернего узла/\"\"\" return 2 \* i + 1
|
||
>
|
||
> def right(self, i: int) -\> int:
|
||
>
|
||
> \"\"\" Получение индекса правого дочернего узла/\"\"\" return 2 \* i + 2
|
||
>
|
||
> def parent(self, i: int) -\> int:
|
||
>
|
||
> \"\"\" Получение индекса родительского узла.\"\"\" return (i - 1) // 2 \# Целочисленное деление вниз.
|
||
|
||
##### Доступ к элементу на вершине кучи
|
||
|
||
> Элемент на вершине кучи -- это корневой узел двоичного дерева, т. е. первый элемент списка.
|
||
>
|
||
> \# === File: my_heap.py === def peek(self) -\> int:
|
||
>
|
||
> \"\"\" Доступ к элементу на вершине кучи.\"\"\"
|
||
>
|
||
> return self.max_heap\[0\]
|
||
|
||
##### Вставка элемента в кучу
|
||
|
||
> Нам дан элемент val, который сначала добавляется в основание кучи. После добавления условия корректности кучи могут быть нарушены, поскольку элемент val может быть больше других элементов кучи. **Поэтому необхо- димо восстановить порядок на пути от вставленного узла до корнево- го узла**. Эта операция называется упорядочиванием кучи.
|
||
>
|
||
> Рассмотрим **выполнение упорядочивания кучи снизу вверх**, начиная с узла, который был добавлен. Как показано на рис. 8.3, необходимо срав- нивать значения вставленного узла и его родительского узла. Если встав- ленный узел больше, они меняются местами. Затем продолжается выпол- нение этой операции с исправлением каждого узла кучи снизу вверх, пока не будет достигнут корневой узел или не встретится узел, который не тре- бует обмена.
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.3.** Этапы добавления элемента в кучу. Шаги 1--3
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.3.** *Продолжение*. Шаги 4--6
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.3.** *Окончание*. Шаги 7--9
|
||
>
|
||
> Пусть общее количество узлов равно *n*, тогда высота дерева будет *O*(log *n*). Из этого следует, что максимальное количество циклов операции упорядочивания кучи также будет *O*(log *n*). Тогда и временная сложность операции добавления элемента в кучу составит *O*(log *n*). Ниже приведен код реализации.
|
||
>
|
||
> \# === File: my_heap.py === def push(self, val: int):
|
||
>
|
||
> \"\"\" Добавление элемента в кучу.\"\"\"
|
||
>
|
||
> \# Добавление узла. self.max_heap.append(val)
|
||
>
|
||
> \# Упорядочивание кучи снизу вверх. self.sift_up(self.size() - 1)
|
||
>
|
||
> def sift_up(self, i: int):
|
||
>
|
||
> \"\"\" Упорядочивание кучи снизу вверх, начиная с узла i.\"\"\" while True:
|
||
>
|
||
> \# Получение родительского узла узла i. p = self.parent(i)
|
||
>
|
||
> \# Если достигнут корневой узел или узел не требует исправления, завер- шение упорядочивания кучи.
|
||
>
|
||
> if p \< 0 or self.max_heap\[i\] \<= self.max_heap\[p\]: break
|
||
>
|
||
> \# Обмен двух узлов. self.swap(i, p)
|
||
>
|
||
> \# Циклическое упорядочивание кучи вверх.
|
||
>
|
||
> i = p
|
||
|
||
##### Извлечение элемента с вершины кучи
|
||
|
||
> Элемент на вершине кучи является корневым узлом двоичного дерева, т. е. первым элементом списка. Если просто удалить первый элемент из списка, индексы всех узлов в двоичном дереве изменятся, что затруднит дальней- шее исправление с помощью упорядочивания кучи. Чтобы минимизиро- вать изменения индексов элементов, используется следующий порядок действий:
|
||
|
||
1) обмен вершины кучи с элементом в основании кучи (обмен корневого узла с самым правым листовым узлом);
|
||
|
||
2) после обмена удаляется элемент в основании кучи из списка (обрати- те внимание, что фактически удаляется исходный элемент на вершине кучи, так как они были поменяны);
|
||
|
||
3) **упорядочивание кучи сверху вниз**, начиная с корневого узла.
|
||
|
||
> **Направление операции упорядочивания кучи сверху вниз противо- положно операции упорядочивания кучи снизу вверх**, как показано на рис. 8.4. Значение корневого узла сравнивается со значениями его двух дочер- них узлов, и самый большой дочерний узел обменивается с корневым узлом. Затем эта операция выполняется циклически, пока не будет достигнут листо- вой узел или не встретится узел, который не требует обмена.
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.4.** Этапы извлечения элемента с вершины кучи. Шаги 1--3
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.4.** *Продолжение*. Шаги 4--6
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.4.** *Продолжение*. Шаги 7--9
|
||
>
|
||
> 
|
||
>
|
||
> **Рис. 8.4.** *Окончание*. Шаг 10
|
||
>
|
||
> Подобно операции добавления элемента в кучу, временная сложность опе- рации извлечения элемента с вершины кучи также составляет *O*(log *n*). Ниже приведен код реализации.
|
||
>
|
||
> \# === File: my_heap.py === def pop(self) -\> int:
|
||
>
|
||
> \"\"\" Извлечение элемента из кучи.\"\"\"
|
||
>
|
||
> \# Обработка пустой кучи. if self.is_empty():
|
||
>
|
||
> raise IndexError(\"Куча пуста\")
|
||
>
|
||
> \# Обмен корневого узла с самым правым листовым узлом (обмен первого и по- следнего элементов).
|
||
>
|
||
> self.swap(0, self.size() - 1) \# Удаление узла.
|
||
>
|
||
> val = self.max_heap.pop()
|
||
>
|
||
> \# Упорядочивание кучи сверху вниз. self.sift_down(0)
|
||
>
|
||
> \# Возврат элемента с вершины кучи. return val
|
||
>
|
||
> def sift_down(self, i: int):
|
||
>
|
||
> \"\"\" Упорядочивание кучи сверху вниз, начиная с узла i.\"\"\" while True:
|
||
>
|
||
> \# Определение узла с максимальным значением среди узлов i, l, r, обо- значенного как ma.
|
||
>
|
||
> l, r, ma = self.left(i), self.right(i), i
|
||
>
|
||
> if l \< self.size() and self.max_heap\[l\] \> self.max_heap\[ma\]: ma = l
|
||
>
|
||
> if r \< self.size() and self.max_heap\[r\] \> self.max_heap\[ma\]: ma = r
|
||
>
|
||
> \# Если узел i максимальный или индексы l, r выходят за пределы, \# упорядочивание кучи не требуется, выход из цикла.
|
||
>
|
||
> if ma == i: break
|
||
>
|
||
> \# Обмен двух узлов. self.swap(i, ma)
|
||
>
|
||
> \# Циклическое упорядочивание кучи вниз. i = ma
|
||
|
||
### Типичные сценарии применения кучи
|
||
|
||
- **Очередь с приоритетом**: куча обычно используется как структура дан- ных для реализации очереди с приоритетом, где операции добавления и извлечения имеют временную сложность *O*(log *n*), а операция постро- ения кучи -- *O*(*n*). Эти операции очень эффективны.
|
||
|
||
- **Пирамидальная сортировка**: для заданного набора данных можно построить кучу, а затем последовательно выполнять операции извле- чения элементов из кучи, чтобы получить отсортированные данные. Однако обычно используется более изящный способ реализации пи- рамидальной сортировки, подробнее см. в разделе «Пирамидальная сортировка».
|
||
|
||
- **Получение** *k* **наибольших элементов**: это классическая задача алго- ритмов и типичное применение. Например, выбор 10 самых популярных новостей для горячих тем в социальных сетях или выбор 10 самых про- даваемых товаров.
|
||
|
||
#### пОСтрОение кучи
|
||
|
||
> В некоторых случаях требуется использовать все элементы списка для постро- ения кучи, этот процесс называется построением кучи.
|
||
|
||
### Реализация с помощью операции добавления в кучу
|
||
|
||
> Сначала создается пустая куча, затем обходится список, и для каждого элемен- та последовательно выполняется операция добавления в кучу. То есть элемент сначала добавляется в основание кучи, а затем для него выполняется упорядо- чивание кучи снизу вверх.
|
||
>
|
||
> При добавлении элемента в кучу ее длина увеличивается на единицу. Посколь- ку узлы добавляются в двоичное дерево сверху вниз, куча строится сверху вниз.
|
||
>
|
||
> Пусть количество элементов равно *n* и каждый элемент добавляется в кучу за время *O*(log *n*). Тогда временная сложность этого метода построения кучи составляет *O*(*n* log *n*).
|
||
|
||
### Реализация через обход и упорядочивание
|
||
|
||
> На самом деле можно реализовать более эффективный метод построения кучи, который состоит из двух шагов:
|
||
|
||
2. Построение кучи ❖ **227**
|
||
|
||
1) добавить все элементы списка в кучу без изменений, при этом свойства кучи еще не соблюдаются;
|
||
|
||
2) обходить кучу в обратном порядке (обратный обход по уровням) и вы- полнять упорядочивание сверху вниз для каждого нелистового узла.
|
||
|
||
> **После упорядочивания узла поддерево с корнем в этом узле становит- ся корректной подкучей**. Поскольку обход осуществляется в обратном по- рядке, куча строится снизу вверх.
|
||
>
|
||
> Выбор обратного обхода обусловлен тем, что он гарантирует, что подде- ревья под текущим узлом уже являются корректными подкучами, что делает упорядочивание текущего узла эффективным.
|
||
>
|
||
> Следует отметить, что **листовые узлы не имеют дочерних узлов**, **поэто- му они естественным образом являются корректными подкучами и не требуют упорядочивания**. Как показано в следующем коде, последний не- листовой узел является родителем последнего узла, и с него начинается об- ратный обход и упорядочивание.
|
||
>
|
||
> \# === File: my_heap.py ===
|
||
>
|
||
> def init (self, nums: list\[int\]):
|
||
>
|
||
> \"\"\" Конструктор, построение кучи на основе входного списка.\"\"\" \# Добавление элементов списка в кучу без изменений. self.max_heap = nums
|
||
>
|
||
> \# Упорядочивание всех узлов, кроме листовых.
|
||
>
|
||
> for i in range(self.parent(self.size() - 1), -1, -1): self.sift_down(i)
|
||
|
||
### Анализ сложности
|
||
|
||
> Теперь попытаемся оценить временную сложность второго метода построе- ния кучи.
|
||
|
||
- Предположим, что количество узлов в полном двоичном дереве равно *n*, тогда количество листовых узлов равно (*n* + 1)/2, где «/» обозначает целочисленное деление вниз. Следовательно, количество узлов, которые необходимо упорядочить, равно (*n* − 1)/2.
|
||
|
||
- В процессе упорядочивания сверху вниз каждый узел может быть упо- рядочен до листового узла, поэтому максимальное количество итераций равно высоте двоичного дерева log *n*.
|
||
|
||
> Умножив эти два значения, можно получить временную сложность процесса построения кучи *O*(*n* log *n*). **Однако эта оценка не точна**, **так как не учиты- вает**, **что количество узлов на нижних уровнях двоичного дерева значи- тельно больше**, **чем на верхних**.
|
||
>
|
||
> Проведем более точный расчет. Чтобы упростить вычисления, предполо- жим, что дано идеальное двоичное дерево с количеством узлов *n* и высотой *h*. Это предположение не повлияет на правильность результата.
|
||
>
|
||
> 
|
||
>
|
||
> **Рис. 8.5.** Количество узлов на каждом уровне идеального двоичного дерева
|
||
>
|
||
> Как видно из рис. 8.5, максимальное количество итераций упорядочивания сверху вниз для узла равно расстоянию от этого узла до листового узла, что соответствует высоте узла. Таким образом, можно суммировать произведения количество узлов × высота узла для каждого уровня, чтобы **получить общее количество итераций упорядочивания для всех узлов**:
|
||
>
|
||
> {width="9.519356955380577e-2in" height="9.505468066491689e-2in"}{width="0.10160761154855644in" height="0.14292979002624673in"} {width="0.10633092738407698in" height="0.14292979002624673in"} .
|
||
>
|
||
> Для упрощения этого выражения воспользуемся знаниями из школьного курса о последовательностях и умножим сначала *T*(*h*) на 2:
|
||
>
|
||
> ,
|
||
>
|
||
> .
|
||
>
|
||
> Используя метод вычитания со сдвигом, вычтем из уравнения 2*T*(*h*) уравне- ние *T*(*h*):
|
||
|
||
{width="0.3446259842519685in" height="0.1429407261592301in"} {width="1.0516010498687665in" height="0.1429407261592301in"} .
|
||
|
||
> {width="7.259623797025372e-2in" height="0.10093722659667542in"}Можно заметить, что *T*(*h*) является геометрической прогрессией, и можно использовать формулу ее суммы, чтобы получить временную сложность:
|
||
|
||
{width="0.3288571741032371in" height="0.16171587926509187in"}.
|
||
|
||
> Далее, количество узлов в идеальном двоичном дереве высоты *h* равно *n* = 2*h*+1 − 1, отсюда легко получить сложность *O*(2*h*) = *O*(*n*). Эти вычисления показывают, что временная сложность построения кучи из входного списка составляет *O*(*n*), что очень эффективно.
|
||
|
||
#### поиск k наибольших элементов
|
||
|
||
> Для решения этой задачи сначала рассмотрим два простых подхода, а затем представим более эффективный метод с использованием кучи.
|
||
|
||
### Первый метод: выбор через обход
|
||
|
||
> Можно выполнить *k* раундов обхода, извлекая в каждом раунде 1-й, 2-й, \..., *k*-й по величине элемент, как показано на рис. 8.6. Временная сложность этого алгоритма составляет *O*(*nk*). Этот метод подходит только для случаев, когда *k* ≪ *n*, так как при *k*, близком к *n*, временная сложность стремится к *O*(*n*2), что очень затратно по времени.
|
||
|
||

|
||
|
||
> **Рис. 8.6.** Поиск k наибольших элементов через обход
|
||
|
||
### Второй метод: сортировка
|
||
|
||
> Можно сначала отсортировать массив nums, а затем вернуть *k* самых правых элементов, как показано на рис. 8.7. Временная сложность этого метода со- ставляет *O*(*n* log *n*). Очевидно, что данный метод перевыполняет задачу, так как необходимо найти лишь *k* наибольших элементов, а не сортировать все остальные.
|
||
|
||

|
||
|
||
> **Рис. 8.7.** Поиск k наибольших элементов с помощью сортировки
|
||
|
||
### Третий метод: куча
|
||
|
||
> Задачу поиска *k* наибольших элементов можно решить более эффективно с по- мощью кучи (см. рис. 8.8).
|
||
|
||
1. Инициализация минимальной кучи, в которой корневой элемент явля- ется наименьшим.
|
||
|
||
2. Вначале в кучу помещаются первые *k* элементов массива.
|
||
|
||
3. Начиная с элемента *k* + 1, если текущий элемент больше корневого эле- мента, то корневой элемент извлекается из кучи, а текущий элемент по- мещается в кучу.
|
||
|
||
4. После завершения обхода в куче остаются *k* наибольших элементов.
|
||
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.8.** Поиск k наибольших элементов с помощью кучи. Шаги 1--3
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.8.** *Продолжение*. Шаги 4--6
|
||
>
|
||
> 
|
||
|
||

|
||
|
||
> **Рис. 8.8.** *Окончание*. Шаги 7--9
|
||
>
|
||
> Ниже приведен пример кода.
|
||
>
|
||
> \# === File: top_k.py ===
|
||
>
|
||
> def top_k_heap(nums: list\[int\], k: int) -\> list\[int\]:
|
||
>
|
||
> \"\"\" Поиск k наибольших элементов в массиве на основе кучи.\"\"\" \# Инициализация минимальной кучи.
|
||
>
|
||
> heap = \[\]
|
||
>
|
||
> \# Помещение первых k элементов массива в кучу. for i in range(k):
|
||
>
|
||
> heapq.heappush(heap, nums\[i\])
|
||
>
|
||
> \# Начиная с элемента k+1, поддержание длины кучи равной k. for i in range(k, len(nums)):
|
||
>
|
||
> \# Если текущий элемент больше корневого элемента, то извлечение корне- вого элемента и помещение текущего элемента в кучу.
|
||
>
|
||
> if nums\[i\] \> heap\[0\]: heapq.heappop(heap) heapq.heappush(heap, nums\[i\])
|
||
>
|
||
> return heap
|
||
>
|
||
> Всего выполняется *n* операций помещения и извлечения из кучи, максималь- ная длина кучи равна *k*, поэтому временная сложность составляет *O*(*n* log *k*). Этот метод очень эффективен: когда *k* мало, временная сложность стремится к *O*(*n*). Когда *k* велико, временная сложность не превышает *O*(*n* log *n*).
|
||
>
|
||
> Кроме того, этот метод подходит для использования в сценариях с динами- ческими потоками данных. При постоянном добавлении данных можно по- стоянно поддерживать элементы в куче, что позволяет динамически обнов- лять *k* наибольших элементов.
|
||
|
||
#### резюме
|
||
|
||
##### Ключевые моменты
|
||
|
||
- Куча представляет собой полное двоичное дерево и может быть двух ти- пов: максимальной и минимальной. Корневой элемент максимальной (минимальной) кучи является наибольшим (наименьшим).
|
||
|
||
- Очередь с приоритетом определяется как очередь с приоритетом извле- чения, обычно реализуемая с помощью кучи.
|
||
|
||
- Основные операции с кучей и их временная сложность включают: по- мещение элемента в кучу *O*(log *n*), извлечение корневого элемента *O*(log *n*) и доступ к корневому элементу *O*(1).
|
||
|
||
- Совершенное двоичное дерево удобно представлять в виде массива, по- этому обычно для хранения кучи используется массив.
|
||
|
||
- Операция упорядочивания кучи используется для поддержания свойств кучи и применяется при операциях помещения и извлечения.
|
||
|
||
- Временную сложность построения кучи из *n* элементов можно оптими- зировать до *O*(*n*), что очень эффективно.
|
||
|
||
- Задача поиска *k* наибольших элементов является классической алгорит- мической задачей и может быть эффективно решена с использованием кучи с временной сложностью *O*(*n* log *k*).
|
||
|
||
> 8.4. Резюме ❖ **235**
|
||
|
||
##### Вопросы и ответы
|
||
|
||
> **Вопрос**. Является ли «куча» в структуре данных тем же понятием, что и «куча» в управлении памятью?
|
||
>
|
||
> **Ответ**. Это не одно и то же понятие, хотя они по случайному стечению обсто- ятельств и имеют одинаковое название. Куча в памяти компьютерной системы является частью динамического распределения памяти, которую программа может использовать для хранения данных во время выполнения. Программа может запросить определенное количество памяти в куче для хранения таких сложных структур, как объекты и массивы. Когда эти данные больше не нуж- ны, программа должна освободить эту память, чтобы предотвратить утечку памяти. В отличие от стека управление и использование памяти в куче требует большей осторожности, неправильное использование может привести к утеч- кам памяти и проблемам с указателями.
|
||
>
|
||
> Глава 9
|