Что такое модуль heapq Python?

28

Я попробовал "heapq" и пришел к выводу, что мои ожидания отличаются от того, что я вижу на экране. Мне нужно, чтобы кто-то объяснил, как это работает и где это может быть полезно.

Из книги Python Module недели в разделе 2.2 Сортировка написано

Если вам нужно сохранить отсортированный список при добавлении и удалении значений, проверьте heapq. Используя функции в heapq для добавления или удаления элементов из списка, вы можете сохранить порядок сортировки списка с помощью низкие накладные расходы.

Вот что я делаю и получаю.

import heapq
heap = []

for i in range(10):
    heap.append(i)

heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

heapq.heapify(heap)    
heapq.heappush(heap, 10)    
heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

heapq.heappop(heap)
0    
heap
[1, 3, 2, 7, 4, 5, 6, 10, 8, 9] <<< Why the list does not remain sorted?

heapq.heappushpop(heap, 11)
1
heap
[2, 3, 5, 7, 4, 11, 6, 10, 8, 9] <<< Why is 11 put between 4 and 6?

Итак, поскольку вы видите, что список "кучи" вообще не отсортирован, на самом деле, чем больше вы добавляете и удаляете предметы, тем они становятся более загроможденными. Выталкиваемые значения берут необъяснимые позиции. Что происходит?

  • 8
    прочитать heapq теорию
  • 0
    Начните извлекать данные из кучи, и вы сами поймете, как данные сортируются в дереве кучи.
Показать ещё 6 комментариев
Теги:
data-structures
heap
python-module

3 ответа

50
Лучший ответ

Модуль heapq поддерживает инвариант кучи, что не то же самое, что поддерживать фактический объект списка в отсортированном порядке.

Цитата из документации heapq:

Кучи - это двоичные деревья, для которых каждый родительский элемент node имеет значение, меньшее или равное любому из его дочерних элементов. В этой реализации используются массивы, для которых heap[k] <= heap[2*k+1] и heap[k] <= heap[2*k+2] для всех k, считая элементы из нуля. Для сравнения несуществующие элементы считаются бесконечными. Интересным свойством кучи является то, что ее наименьший элемент всегда является корнем, heap[0].

Это означает, что очень удобно находить наименьший элемент (просто возьмите heap[0]), что отлично подходит для очереди приоритетов. После этого следующие 2 значения будут больше (или равны), чем 1-е, а следующие 4 после этого будут больше, чем их "родительский" node, тогда следующие 8 больше и т.д.

Вы можете больше узнать о теории структуры данных в разделе Theory документации. Вы также можете посмотреть эту лекцию из курса MIT OpenCourseWare Введение в алгоритмы, который объясняет алгоритм в общих чертах.

Куча может быть возвращена в отсортированный список очень эффективно:

def heapsort(heap):
    return [heapq.heappop(heap) for _ in range(len(heap))]

просто щелкнув следующий элемент из кучи. Однако использование sorted(heap) должно быть более быстрым, поскольку TimSort воспользуется частичным заказом, уже присутствующим в куче.

Вы использовали бы кучу, если вас интересует только наименьшее значение или первые наименьшие значения n, особенно если вы заинтересованы в этих значениях на постоянной основе; добавление новых элементов и удаление самого маленького очень эффективно, тем более, что прибегать к списку при каждом добавлении значения.

  • 0
    Может быть, я неправильно понимаю, но: «После этого следующие 2 значения будут больше (или равны), чем 1-е, а следующие 4 после этого будут больше, чем первые 3, затем следующие 8 будут больше и т. Д. " - в качестве контрпримера: [1, 5, 9, 7, 15, 10, 11] - допустимая двоичная минимальная куча, но, например, 7 (третий уровень в иерархии) все еще меньше 9 (второй уровень в иерархии). Упорядоченное свойство в куче верно только для обхода родитель-потомок, но не обязательно для отношений «тетя-племянница».
  • 0
    @DanielAndersson: да, это предложение было упрощено и, благодаря упрощению, теперь в основном неверно. Спасибо что подметил это!
Показать ещё 4 комментария
19

Ваша книга неверна! Как вы демонстрируете, куча не является отсортированным списком (хотя отсортированный список - это куча). Что такое куча? Чтобы процитировать руководство по проектированию алгоритма Skiena

Кучи - это простая и элегантная структура данных для эффективной поддержки операций вставки очереди приоритетов и извлечения-мин. Они работают, поддерживая частичный порядок на множестве элементов, который слабее упорядоченного порядка (поэтому он может быть эффективным для поддержания), но более сильного, чем случайный порядок (поэтому минимальный элемент можно быстро идентифицировать).

По сравнению с отсортированным списком куча подчиняется более слабому условию кучи-инварианта. Прежде чем определить его, сначала подумайте, почему расслабление может оказаться полезным. Ответ заключается в том, что более слабое состояние легче поддерживать. Вы можете делать меньше с кучей, но вы можете сделать это быстрее.

Куча имеет три операции:

  • Find-Minimum - O (1)
  • Вставить O (log n)
  • Удалить-Min O (log n)

Crucially Insert - это O (log n), который превосходит O (n) для отсортированного списка.

Что такое инвариант кучи? "Бинарное дерево, где родители доминируют над своими детьми". То есть "p ≤ c для всех детей c из p". Skiena иллюстрирует изображения и продолжает демонстрировать алгоритм вставки элементов при сохранении инварианта. Если вы подумаете, вы можете сами их изобрести. (Подсказка: они известны как пузырь и пузырь вниз)

Хорошей новостью является то, что в комплекте с Python используются все элементы для вас, в модуле heapq. Он не определяет тип кучи (который, я думаю, будет проще использовать), но предоставляет их в качестве вспомогательных функций в списке.

Мораль: Если вы пишете алгоритм, используя отсортированный список, но только проверяете и удаляете его с одного конца, вы можете сделать алгоритм более эффективным с помощью кучи.

Для проблемы, в которой полезна структура данных кучи, читайте https://projecteuler.net/problem=500

  • 0
    Четкое и ясное объяснение
  • 0
    Как вы сравниваете эффективность hashtable (словарь в Python) и таблицы кучи для выполнения вставки / удаления? Я знаю, что для хеш-таблицы O (1) в лучшем случае и O (n) в худшем случае. O (log n) для худшего или среднего случая кучи?
18

Существует некоторая непонимания реализации структуры данных кучи. Модуль heapq фактически является вариантом реализации двоичной кучи, где элементы кучи хранятся в списке, как описано здесь: https://en.wikipedia.org/wiki/Binary_heap#Heap_implementation

Цитата из Википедии:

Кучи обычно реализуются с помощью массива. Любое двоичное дерево может быть сохранено в массиве, но поскольку двоичная куча всегда является полным бинарным деревом, ее можно хранить компактно. Для указателей не требуется пространство; вместо этого родительский элемент и дочерние элементы каждого из них node могут быть найдены путем арифметики по индексам массива.

Этот снимок ниже поможет вам почувствовать разницу между представлением дерева и списком кучи и (обратите внимание, что это максимальная куча, которая является обратной обычной мини-куче!):

Изображение 125132

В целом структура данных кучи отличается от сортированного списка тем, что она жертвует некоторой информацией о том, больше или меньше какой-либо конкретный элемент, чем любой другой. Куча только может сказать, что этот конкретный элемент меньше, чем его родительский и больший, чем его дети. Чем меньше информации хранится структура данных, тем меньше времени/памяти требуется для ее изменения. Сравните сложность некоторых операций между кучей и отсортированным массивом:

        Heap                  Sorted array
        Average  Worst case   Average   Worst case

Space   O(n)     O(n)         O(n)      O(n)

Search  O(n)     O(n)         O(log n)  O(log n)

Insert  O(1)     O(log n)     O(n)      O(n)

Delete  O(log n) O(log n)     O(n)      O(n)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню