heapq.md
1> **Источник:** https://python-all.ru/3.0/library/heapq.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `heapq` – алгоритм очереди с приоритетом (куча)89Этот модуль реализует алгоритм очереди с кучей, также известный как алгоритм приоритетной очереди.1011Кучи – это массивы, для которых `heap[k] <= heap[2*k+1]` и `heap[k] <= heap[2*k+2]` для всех *k*, при нумерации элементов с нуля. Для сравнения несуществующие элементы считаются бесконечными. Интересное свойство кучи: `heap[0]` всегда является наименьшим элементом.1213Приведённый ниже API отличается от классических алгоритмов работы с кучами в двух аспектах: (a) используется индексация с нуля. Это делает связь между индексом узла и индексами его потомков чуть менее очевидной, но более подходящей, поскольку Python использует индексацию с нуля. (b) Метод pop возвращает наименьший элемент, а не наибольший (в учебниках это называется «min heap»; «max heap» чаще встречается в литературе из-за его пригодности для сортировки на месте).1415Эти два свойства позволяют рассматривать кучу как обычный список Python без сюрпризов: `heap[0]` – наименьший элемент, а `heap.sort()` сохраняет инвариант кучи!1617Чтобы создать кучу, используйте список, инициализированный значением `[]`, или можно преобразовать заполненный список в кучу с помощью функции [`heapify()`](https://python-all.ru/3.0/library/heapq.html#heapq.heapify).1819Предоставляются следующие функции:2021#### `[heapq.heappush]heapq.heappush(heap, item)`2223Помещает значение2425*item*2627в2829*heap*3031, сохраняя инвариант кучи.3233#### `[heapq.heappop]heapq.heappop(heap)`3435Извлекает и возвращает наименьший элемент из3637*кучи*3839, сохраняя инвариант кучи. Если куча пуста, возникает исключение4041[`IndexError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.IndexError)4243.4445#### `[heapq.heappushpop]heapq.heappushpop(heap, item)`4647Помещает4849*item*5051в кучу, затем извлекает и возвращает наименьший элемент из5253*heap*5455. Совместное действие выполняется эффективнее, чем5657[`heappush()`](https://python-all.ru/3.0/library/heapq.html#heapq.heappush)5859с последующим отдельным вызовом6061[`heappop()`](https://python-all.ru/3.0/library/heapq.html#heapq.heappop)6263.6465#### `[heapq.heapify]heapq.heapify(x)`6667Преобразует список6869*x*7071в кучу на месте за линейное время.7273#### `[heapq.heapreplace]heapq.heapreplace(heap, item)`7475Извлекает и возвращает наименьший элемент из *heap*, а также помещает новый *item*. Размер кучи не изменяется. Если куча пуста, возбуждается [`IndexError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.IndexError). Это более эффективно, чем [`heappop()`](https://python-all.ru/3.0/library/heapq.html#heapq.heappop), за которым следует [`heappush()`](https://python-all.ru/3.0/library/heapq.html#heapq.heappush), и может быть более подходящим при использовании кучи фиксированного размера. Обратите внимание, что возвращаемое значение может быть больше, чем *item*! Это ограничивает разумное использование данной функции, если она не является частью условной замены:7677```python78if item > heap[0]:79 item = heapreplace(heap, item)80```8182Пример использования:8384```python85>>> from heapq import heappush, heappop86>>> heap = []87>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]88>>> for item in data:89... heappush(heap, item)90...91>>> ordered = []92>>> while heap:93... ordered.append(heappop(heap))94...95>>> ordered96[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]97>>> data.sort()98>>> data == ordered99True100```101102Использование кучи для вставки элементов в нужное место очереди с приоритетом:103104```python105>>> heap = []106>>> data = [(1, 'J'), (4, 'N'), (3, 'H'), (2, 'O')]107>>> for item in data:108... heappush(heap, item)109...110>>> while heap:111... print(heappop(heap)[1])112J113O114H115N116```117118Модуль также предоставляет три универсальные функции на основе куч.119120#### `[heapq.merge]heapq.merge(*iterables)`121122Сливает несколько отсортированных входных последовательностей в одну отсортированную выходную (например, объединяет записи с временными метками из нескольких файлов журналов). Возвращает [*итератор*](https://python-all.ru/3.0/glossary.html#term-iterator) по отсортированным значениям.123124Похож на `sorted(itertools.chain(*iterables))`, но возвращает итератор, не загружает все данные в память сразу и предполагает, что каждый из входных потоков уже отсортирован (от меньшего к большему).125126#### `[heapq.nlargest]heapq.nlargest(n, iterable[, key])`127128Возвращает список из129130*n*131132наибольших элементов из набора данных, заданного133134*iterable*135136. Если указан137138*key*139140, он задаёт функцию одного аргумента, используемую для извлечения ключа сравнения из каждого элемента итератора:141142`key=str.lower`143144Эквивалентно:145146`sorted(iterable, key=key, reverse=True)[:n]`147148#### `[heapq.nsmallest]heapq.nsmallest(n, iterable[, key])`149150Возвращает список из151152*n*153154наименьших элементов из набора данных, заданного155156*iterable*157158. Если указан159160*key*161162, он задаёт функцию одного аргумента, используемую для извлечения ключа сравнения из каждого элемента итератора:163164`key=str.lower`165166Эквивалентно:167168`sorted(iterable, key=key)[:n]`169170Последние две функции наиболее эффективны для небольших значений *n*. Для больших значений эффективнее использовать функцию [`sorted()`](https://python-all.ru/3.0/library/functions.html#sorted). Кроме того, когда `n==1`, эффективнее использовать встроенные функции [`min()`](https://python-all.ru/3.0/library/functions.html#min) и [`max()`](https://python-all.ru/3.0/library/functions.html#max).171172## Теория173174(Это пояснение принадлежит Франсуа Пинару. Код Python для этого модуля был предоставлен Кевином О’Коннором.)175176Кучи – это массивы, для которых `a[k] <= a[2*k+1]` и `a[k] <= a[2*k+2]` для всех *k*, считая элементы с нуля. Для сравнения несуществующие элементы считаются бесконечными. Интересное свойство кучи заключается в том, что `a[0]` всегда является её наименьшим элементом.177178Странный инвариант выше предназначен для эффективного представления турнира в памяти. Числа ниже – это *k*, а не `a[k]`:179180```python181 0182183 1 2184185 3 4 5 6186187 7 8 9 10 11 12 13 1418818915 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30190```191192В дереве выше каждая ячейка *k* находится над `2*k+1` и `2*k+2`. В обычном бинарном турнире, который мы видим в спорте, каждая ячейка является победителем над двумя ячейками, которые она возглавляет, и мы можем проследить победителя вниз по дереву, чтобы увидеть всех его противников. Однако во многих компьютерных приложениях таких турниров нам не нужно отслеживать историю победителя. Чтобы быть более эффективными по памяти, когда победитель продвигается вверх, мы пытаемся заменить его чем-то другим на более низком уровне, и правило становится таким, что ячейка и две ячейки, которые она возглавляет, содержат три разных элемента, но верхняя ячейка «выигрывает» у двух нижних ячеек.193194Если этот инвариант кучи всегда сохраняется, то индекс 0, очевидно, является общим победителем. Самый простой алгоритмический способ удалить его и найти «следующего» победителя – переместить какого-то проигравшего (скажем, ячейку 30 на диаграмме выше) на позицию 0, а затем просеивать этот новый 0 вниз по дереву, обменивая значения, пока инвариант не будет восстановлен. Это, очевидно, логарифмическая зависимость от общего количества элементов в дереве. Перебирая все элементы, получается сортировка O(n log n).195196Хорошая особенность такой сортировки в том, что можно эффективно вставлять новые элементы, пока сортировка выполняется, при условии, что вставляемые элементы не «лучше» последнего извлечённого элемента с индексом 0. Это особенно полезно в контексте симуляций, где дерево хранит все входящие события, а условие «выигрыша» означает наименьшее запланированное время. Когда событие планирует выполнение других событий, они планируются на будущее, так что их легко добавить в кучу. Таким образом, куча – хорошая структура для реализации планировщиков (именно это я использовал для своего MIDI-секвенсора :-).197198Различные структуры для реализации планировщиков были тщательно изучены, и кучи для этого хороши: они достаточно быстры, скорость почти постоянна, и наихудший случай мало отличается от среднего. Однако существуют и другие представления, которые в целом более эффективны, хотя наихудшие случаи могут быть ужасными.199200Кучи также очень полезны при большой сортировке на диске. Вероятно, всем известно, что большая сортировка подразумевает создание «прогонов» (предварительно отсортированных последовательностей, размер которых обычно связан с объемом оперативной памяти), за которыми следуют проходы слияния для этих прогонов, причем слияние часто организовано очень умно [\[1\]](https://python-all.ru/3.0/library/heapq.html#id2). Очень важно, чтобы начальная сортировка создавала как можно более длинные прогоны. Турниры – хороший способ для этого. Если, используя всю доступную память для проведения турнира, заменять и просеивать элементы, которые вписываются в текущий прогон, то получаются прогоны, вдвое превышающие размер памяти для случайного ввода, и гораздо лучше для ввода с нечеткой упорядоченностью.201202Более того, если вы выводите нулевой элемент на диск и получаете входное значение, которое не помещается в текущий турнир (потому что оно «выигрывает» у последнего выведенного значения), то оно не может поместиться в кучу, и размер кучи уменьшается. Освободившуюся память можно сразу с умом использовать для постепенного построения второй кучи, которая растёт ровно с той же скоростью, с какой тает первая. Когда первая куча полностью исчезает, вы переключаетесь на вторую и начинаете новую серию. Умно и весьма эффективно!203204Одним словом, кучи – полезные структуры данных, которые стоит знать. Я использую их в нескольких приложениях и считаю, что полезно иметь модуль «heap» под рукой. :-)205206Сноски207208| [\[1\]](https://python-all.ru/3.0/library/heapq.html#id1) | Современные алгоритмы балансировки дисков раздражают больше, чем впечатляют, и это следствие возможностей позиционирования дисков. На устройствах без произвольного доступа, таких как большие ленточные накопители, ситуация была совсем иной, и приходилось проявлять большую изобретательность, чтобы заранее гарантировать, что каждое перемещение ленты будет максимально эффективным (то есть наилучшим образом способствует «продвижению» слияния). Некоторые ленты умели даже читать в обратном направлении, и это использовали, чтобы избежать перемотки. Поверьте, по-настоящему хорошие ленточные сортировки были впечатляющим зрелищем! Во все времена сортировка была Великим Искусством! :-) |209| --- | --- |210