Как работает heapq.nlargest?
Я смотрел на этот разговор по пикону, 34:30, и оратор говорит, что получение t
самые большие элементы списка n
элементы могут быть сделаны в O(t + n)
,
Как это возможно? Я понимаю, что создание кучи будет O(n)
, а в чем сложность nlargest
само по себе, это O(n + t)
или же O(t)
(и каков фактический алгоритм)?
4 ответа
Динамик не прав в этом случае. Фактическая стоимость O(n * log(t))
, Heapify вызывается только на первом t
элементы повторяемы. Это O(t)
, но незначительно, если t
намного меньше чем n
, Затем все остальные элементы добавляются в эту "маленькую кучу" через heappushpop
, один за раз. Это занимает O(log(t))
время на вызов heappushpop
, Длина кучи остается t
на протяжении. В самом конце сортируется куча, которая стоит O(t * log(t))
, но это также незначительно, если t
намного меньше чем n
,
Весело с теорией;-)
Существуют достаточно простые способы найти самый большой элемент в ожидаемом O(n)
время; например, смотрите здесь. Есть худшие способы сделать это в худшем случае O(n)
время. Затем, еще раз пройдя через вход, вы можете вывести t
элементы>= t-й по величине (с утомительными осложнениями в случае дубликатов). Таким образом, вся работа может быть выполнена в O(n)
время.
Но эти способы требуют O(n)
память тоже. Python не использует их. Преимущество того, что на самом деле реализовано, заключается в том, что "дополнительная" нагрузка на память в худшем случае O(t)
и это может быть очень значительным, когда входной сигнал представляет собой, например, генератор, выдающий очень много значений.
для heapq t наибольшего или t наименьшего временная сложность будет
O(nlog(t))
heapq создаст кучу для первых элементов t, а затем будет перебирать оставшиеся элементы, нажимая и вставляя элементы из кучи (сохраняя элементы t в куче.
- для построения кучи для первых t элементов будет сделано
- для нажатия и вставки остальных элементов будет выполнено в
(n-k)log(t)
- Общая временная сложность будет
nlog(t)
Согласно комментариям в исходном коде heapq :
# Algorithm notes for nlargest() and nsmallest()
# ==============================================
#
# Make a single pass over the data while keeping the k most extreme values
# in a heap. Memory consumption is limited to keeping k values in a list.
#
# Theoretical number of comparisons for k smallest of n random inputs:
#
# Step Comparisons Action
# ---- -------------------------- ---------------------------
# 1 1.66 * k heapify the first k-inputs
# 2 n - k compare remaining elements to top of heap
# 3 k * (1 + lg2(k)) * ln(n/k) replace the topmost value on the heap
# 4 k * lg2(k) - (k/2) final sort of the k most extreme values
#
# Combining and simplifying for a rough estimate gives:
#
# comparisons = n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k))
На самом деле это O (n + t log (n)), потому что heapify принимает O (n), а для каждого элемента наибольшего или наименьшего размера принимает O (log (n)). Таким образом, для t наибольшего / наименьшего требуется t log (n). Следовательно, временная сложность будет O (n + t * log (n))