Как построение кучи может быть O (n) временной сложностью?

494

Может кто-нибудь помочь объяснить, как сборка кучи может быть O (n) сложность?

Вставка элемента в кучу происходит O(log n), и вставка повторяется n / 2 раза (остальные - листья и не могут нарушать свойство кучи). Таким образом, это означает, что сложность должна быть O(n log n), я думаю.

Другими словами, для каждого элемента, который мы «heapify», он может фильтровать один раз для каждого уровня кучи (то есть log n уровней).

Что мне не хватает?

GBA
источник
что именно вы подразумеваете под "сборкой" кучи?
mfrankli
Как и в случае с heapsort, возьмите несортированный массив и отфильтруйте каждый из верхних элементов, пока он не будет соответствовать правилам кучи
GBa
2
Единственной вещью, которую я смог найти, была эта ссылка: сложность Buildheap, по-видимому, равна calls (n lg n) - n вызовов Heapify по цене Θ (lg n) за вызов, но этот результат можно улучшить до Θ (n) cs.txstate.edu/~ch04/webtest/teaching/courses/5329/lectures/…
GBa
2
@Gba смотрите это видео из Массачусетского технологического института: он хорошо объясняет, как мы получаем O (n), с небольшой
долей
2
Прямая ссылка на упомянутое объяснение @CodeShadow: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

Ответы:

435

Я думаю, что в этой теме похоронено несколько вопросов:

  • Как вы реализуете, buildHeapчтобы он работал в O (N) времени?
  • Как вы показываете, что buildHeapработает в O (N) времени при правильной реализации?
  • Почему та же самая логика не работает для того, чтобы сортировка кучи выполнялась за O (n), а не за O (n log n) ?

Как вы реализуете, buildHeapчтобы он работал в O (N) времени?

Часто ответы на эти вопросы сосредоточены на разнице между siftUpи siftDown. Делая правильный выбор между siftUpи siftDownимеет решающее значение для получения (п) O производительность buildHeap, но ничего не делает , чтобы помочь человеку не понять разницу между buildHeapи heapSortв целом. Действительно, правильные реализации обоих так buildHeapи heapSortбудут использовать толькоsiftDown . Эта siftUpоперация необходима только для вставки в существующую кучу, поэтому она будет использоваться для реализации очереди с приоритетами, например, с использованием двоичной кучи.

Я написал это, чтобы описать, как работает максимальная куча. Этот тип кучи обычно используется для сортировки кучи или для очереди с приоритетами, где более высокие значения указывают на более высокий приоритет. Мин куча также полезна; например, при извлечении элементов с целочисленными ключами в порядке возрастания или строк в алфавитном порядке. Принципы точно такие же; просто переключите порядок сортировки.

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

  • siftDown меняет узел, который является слишком маленьким, с его самым большим дочерним элементом (тем самым перемещая его вниз), пока он не станет по крайней мере таким же большим, как оба узла под ним.
  • siftUp меняет узел, который слишком велик, с его родителем (тем самым перемещая его вверх), пока он не станет больше, чем узел над ним.

Количество операций, необходимых для siftDownи siftUpпропорционально расстоянию, которое может пройти узел. Ведь siftDownэто расстояние до нижней части дерева, поэтому siftDownоно дорого для узлов в верхней части дерева. При siftUpэтом работа пропорциональна расстоянию до вершины дерева, поэтому siftUpстоит дорого для узлов в нижней части дерева. Хотя в худшем случае обе операции равны O (log n) , в куче только один узел находится вверху, тогда как половина узлов лежит на нижнем уровне. Поэтому не должно быть слишком удивительно, что если нам нужно применить операцию к каждому узлу, мы бы предпочли siftDownболее siftUp.

buildHeapФункция принимает массив неупорядоченных элементов и перемещает их , пока они все не удовлетворяют кучного собственности, в результате чего получают действительную кучу. Есть два подхода можно было бы принять для buildHeapиспользования siftUpи siftDownопераций мы описали.

  1. Начните с верхней части кучи (начало массива) и вызовите siftUpкаждый элемент. На каждом шаге ранее просеянные элементы (элементы перед текущим элементом в массиве) образуют правильную кучу, а отсеивание следующего элемента помещает его в правильную позицию в куче. После просеивания каждого узла все элементы удовлетворяют свойству кучи.

  2. Или идите в противоположном направлении: начните с конца массива и двигайтесь назад вперед. На каждой итерации вы просеиваете предмет, пока он не окажется в правильном месте.

Какая реализация для buildHeapболее эффективна?

Оба эти решения будут создавать допустимую кучу. Неудивительно, что более эффективной является вторая операция, которая использует siftDown.

Пусть h = log n представляет высоту кучи. Требуемая для siftDownподхода работа определяется суммой

(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).

Каждый член в сумме имеет максимальное расстояние, которое должен пройти узел на данной высоте (ноль для нижнего слоя, h для корня), умноженное на количество узлов на этой высоте. Напротив, сумма для вызова siftUpна каждом узле

(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).

Должно быть понятно, что вторая сумма больше. Одно только первое слагаемое имеет вид hn / 2 = 1/2 n log n , поэтому такой подход в лучшем случае имеет сложность O (n log n) .

Как мы докажем, что сумма для siftDownподхода действительно O (n) ?

Один из методов (есть и другие анализы, которые также работают) - превратить конечную сумму в бесконечный ряд, а затем использовать ряд Тейлора. Мы можем игнорировать первый член, который равен нулю:

Серия Тейлора для сложности buildHeap

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

  • Все члены являются положительными, поэтому конечная сумма должна быть меньше бесконечной суммы.
  • Ряд равен степенному ряду, оцененному в x = 1/2 .
  • Этот степенной ряд равен (постоянному времени) производной ряда Тейлора для f (x) = 1 / (1-x) .
  • х = 1/2 находится в интервале сходимости этого ряда Тейлора.
  • Следовательно, мы можем заменить ряд Тейлора на 1 / (1-x) , дифференцировать и оценить, чтобы найти значение бесконечного ряда.

Поскольку бесконечная сумма равна ровно n , мы заключаем, что конечная сумма не больше, и, следовательно, O (n) .

Почему сортировка кучи требует времени O (n log n) ?

Если можно работать buildHeapза линейное время, почему сортировка кучи требует времени O (n log n) ? Ну, куча сортировки состоит из двух этапов. Сначала мы обращаемся buildHeapк массиву, который требует оптимального времени O (n) . Следующим этапом является повторное удаление самого большого элемента в куче и помещение его в конец массива. Поскольку мы удаляем элемент из кучи, сразу после окончания кучи всегда есть открытое место, где мы можем сохранить элемент. Таким образом, сортировка кучи достигает отсортированного порядка, последовательно удаляя следующий по величине элемент и помещая его в массив, начиная с последней позиции и двигаясь вперед. Это сложность этой последней части, которая доминирует в куче. Цикл выглядит так:

for (i = n - 1; i > 0; i--) {
    arr[i] = deleteMax();
}

Ясно, что цикл выполняется O (n) раз ( точнее, n - 1 , последний элемент уже на месте). Сложность deleteMaxдля кучи составляет O (log n) . Обычно это выполняется путем удаления корня (самый большой элемент, оставшийся в куче) и замены его последним элементом в куче, который является листом и, следовательно, одним из самых маленьких элементов. Этот новый корень почти наверняка нарушит свойство кучи, поэтому вы должны вызывать его, siftDownпока не вернете его обратно в приемлемое положение. Это также приводит к перемещению следующего по величине элемента до корня. Обратите внимание, что в отличие от того, buildHeapгде для большинства узлов мы вызываем siftDownиз нижней части дерева, мы теперь вызываем siftDownиз верхней части дерева на каждой итерации!Хотя дерево сжимается, оно не сжимается достаточно быстро : высота дерева остается постоянной, пока вы не удалите первую половину узлов (когда вы полностью очистите нижний слой). Тогда для следующей четверти высота h - 1 . Таким образом, общая работа для этого второго этапа

h*n/2 + (h-1)*n/4 + ... + 0 * 1.

Обратите внимание на переключение: теперь нулевой рабочий случай соответствует одному узлу, а h рабочий случай соответствует половине узлов. Эта сумма равна O (n log n) так же, как неэффективная версия, buildHeapкоторая реализована с использованием siftUp. Но в этом случае у нас нет выбора, так как мы пытаемся отсортировать, и мы требуем, чтобы следующий самый большой элемент был удален следующим.

Таким образом, работа по сортировке кучи является суммой двух этапов: O (n) время для buildHeap и O (n log n) для удаления каждого узла по порядку , поэтому сложность составляет O (n log n) . Вы можете доказать (используя некоторые идеи из теории информации), что для сортировки на основе сравнения O (n log n) - лучшее, на что вы можете надеяться, так что нет никаких причин разочаровываться этим или ожидать, что сортировка кучи достигнет O (N) ограничено по времени, что buildHeapделает.

Джереми Уэст
источник
2
Я отредактировал свой ответ, чтобы использовать максимальную кучу, поскольку кажется, что большинство других людей ссылаются на это, и это лучший выбор для сортировки кучи.
Джереми Вест
28
Это то, что сделало для меня интуитивно понятным: «только один узел находится наверху, тогда как половина узлов лежит на нижнем слое. Поэтому не должно быть слишком удивительным, что, если нам придется применить операцию к каждому узлу, мы бы предпочитаю siftDown, а не siftUp. "
Вики Chijwani
3
@JeremyWest "Один из них - начать с верхней части кучи (начало массива) и вызвать siftUp для каждого элемента." - Вы хотели начать с нижней части кучи?
aste123
4
@ aste123 Нет, это правильно, как написано. Идея состоит в том, чтобы поддерживать барьер между частью массива, которая удовлетворяет свойству кучи, и несортированной частью массива. Вы либо начинаете в начале, двигаясь вперед и вызывая siftUpкаждый предмет, либо начинаете в конце, двигаясь назад и звоните siftDown. Независимо от того, какой подход вы выберете, вы выбираете следующий элемент в несортированной части массива и выполняете соответствующую операцию, чтобы переместить его в правильную позицию в упорядоченной части массива. Разница лишь в производительности.
Джереми Уэст
2
Это лучший ответ, который я когда-либо видел на любой вопрос в мире. Это было так хорошо объяснено, я как будто это действительно возможно ... спасибо большое.
HARSHIL JAIN
314

Ваш анализ верен. Тем не менее, это не тесно.

Нелегко объяснить, почему построение кучи является линейной операцией, вам лучше ее прочитать.

Большой анализ алгоритма можно увидеть здесь .


Основная идея заключается в том, что в build_heapалгоритме фактическая heapifyстоимость указана не O(log n)для всех элементов.

Когда heapifyвызывается, время выполнения зависит от того, как далеко элемент может сместиться вниз по дереву, прежде чем процесс завершится. Другими словами, это зависит от высоты элемента в куче. В худшем случае элемент может опуститься до уровня листа.

Давайте посчитаем проделанную работу уровень за уровнем.

На самом нижнем уровне есть 2^(h)узлы, но мы не обращаемся heapifyни к одному из них, поэтому работа равна 0. На следующем уровне находятся 2^(h − 1)узлы, и каждый может перейти вниз на 1 уровень. На 3-м уровне снизу есть 2^(h − 2)узлы, и каждый может опуститься на 2 уровня.

Как видите, не все операции heapify выполняются O(log n), поэтому вы получаете O(n).

эмре невайширази
источник
17
Это отличное объяснение ... но почему тогда сортировка кучи выполняется в O (n log n). Почему те же рассуждения не применимы к сортировке кучи?
2013 г.
49
@ HBA Я думаю, что ответ на ваш вопрос заключается в понимании этого изображения из этой статьи . Heapifyэто O(n)когда сделано с siftDownно O(n log n)когда сделано с siftUp. Фактическая сортировка (извлечение элементов из кучи один за другим) должна выполняться siftUpтаким образом O(n log n).
The111
3
Мне очень нравится интуитивное объяснение вашего внешнего документа внизу.
Лукас Гребликас
1
@hba Ответ Джереми Уэста, приведенный ниже, рассматривает ваш вопрос более подробно и легко, подробно объясняя ответ на комментарий The111 здесь.
виолончель
Вопрос. Мне кажется, что # сравнения, сделанные для узла на высоте iот нижней части дерева с высотой h, должны также делать 2* log(h-i)сравнения и должны учитываться также @ The111. Что вы думаете?
Сид
94

Наглядно:

«Сложность должна быть O (nLog n) ... для каждого элемента, который мы« heapify », у него есть потенциальная необходимость фильтровать один раз для каждого уровня для кучи до сих пор (то есть log n уровней)».

Не совсем. Ваша логика не дает жесткой границы - она ​​переоценивает сложность каждой кучи. Если построено снизу вверх, вставка (heapify) может быть намного меньше, чем O(log(n)). Процесс выглядит следующим образом:

(Шаг 1) Первые n/2элементы идут в нижнем ряду кучи. h=0, так что heapify не нужен.

(Шаг 2) Следующие элементы идут в строке 1 снизу вверх. Фильтры на 1 уровень вниз.n/22h=1

(Шаг i ) Следующие элементы идут в ряд снизу вверх. Отфильтруйте фильтры уровнями вниз.n/2iih=ii

( Журнал шагов (n) ) Последний элемент идет в ряд снизу вверх. Отфильтруйте фильтры уровнями вниз.n/2log2(n) = 1log(n)h=log(n)log(n)

ЗАМЕЧАНИЕ: после первого шага 1/2элементы (n/2)уже находятся в куче, и нам даже не нужно было вызывать heapify один раз. Кроме того, обратите внимание, что только один элемент, корень, фактически несет полную log(n)сложность.


Теоретически:

Всего шагов Nдля построения кучи размера n, можно записать математически.

На высоте iмы показали (выше), что будут элементы, которые нужно вызвать heapify, и мы знаем, что heapify на высоте is . Это дает:n/2i+1iO(i)

введите описание изображения здесь

Решение последней суммы можно найти, взяв производную обеих сторон хорошо известного уравнения геометрической серии:

введите описание изображения здесь

Наконец, подключение x = 1/2к приведенному выше уравнению дает 2. Включение этого в первое уравнение дает:

введите описание изображения здесь

Таким образом, общее количество шагов имеет размер O(n)

bcorso
источник
35

Было бы O (n log n), если бы вы строили кучу, многократно вставляя элементы. Тем не менее, вы можете создать новую кучу более эффективно, вставляя элементы в произвольном порядке, а затем применяя алгоритм для «кучи» их в правильном порядке (конечно, в зависимости от типа кучи).

См. Http://en.wikipedia.org/wiki/Binary_heap , «Создание кучи» для примера. В этом случае вы, по сути, работаете с нижнего уровня дерева, меняя местами родительский и дочерний узлы, пока не будут выполнены условия кучи.

mike__t
источник
12

Уже есть несколько отличных ответов, но я хотел бы добавить небольшое визуальное объяснение

введите описание изображения здесь

Теперь взгляните на изображение: есть
n/2^1 зеленые узлы с высотой 0 (здесь 23/2 = 12),
n/2^2 красные узлы с высотой 1 (здесь 23/4 = 6)
n/2^3 синие узлы с высотой 2 (здесь 23/8 = 3)
n/2^4 фиолетовые узлы с высотой 3 (здесь 23/16 = 2),
поэтому есть n/2^(h+1)узлы для высоты h.
Чтобы определить сложность времени, давайте посчитаем количество выполненной работы или максимальное количество итераций, выполненных каждым узлом,
теперь можно заметить, что каждый узел может выполнить (максимум) итераций == высота узла

Green  = n/2^1 * 0 (no iterations since no children)  
red    = n/2^2 * 1 (heapify will perform atmost one swap for each red node)  
blue   = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)  
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)   

поэтому для любых узлов с высотой h максимальная работа равна n / 2 ^ (h + 1) * h

В настоящее время общая работа выполнена

->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)  
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

Теперь для любого значения ч , последовательность

-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

никогда не будет превышать 1
Таким образом, сложность времени никогда не будет превышать O (n) для построения кучи

Julkar9
источник
7

Как мы знаем, высота кучи равна log (n) , где n - общее количество элементов. Представим его как h.
   Когда мы выполняем операцию heapify, элементы на последнем уровне ( h ) не будут перемещаться даже на единицу. шаг.
   Количество элементов на втором последнем уровне ( h-1 ) равно 2 h-1, и они могут двигаться на уровне максимум 1 (во время heapify).
   Аналогичным образом , для я - го , уровня мы имеем 2 я элементы , которые могут перемещаться привет позиции.

Поэтому общее количество ходов = S = 2 ч * 0 + 2 ч-1 * 1 + 2 ч-2 * 2 + ... 2 0 * ч

                                               S = 2 ч {1/2 + 2/2 2 + 3/2 3 + ... ч / 2 ч } ----------------------- -------------------------- 1
это серия AGP , для решения этого делим обе стороны на 2
                                               S / 2 = 2 ч {1/2 2 + 2/2 3 + ... ч / 2 ч + 1 } --------------------------------- ---------------- 2
вычитая уравнение 2 из 1, получаем
                                               S / 2 = 2 ч { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 ч + ч / 2 ч + 1 }
                                               S = 2 ч + 1 {1/2 + 1/2 2 + 1/2 3 + ... 1/2 ч + ч / 2 ч + 1 }
Теперь 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h - уменьшение GP , сумма которого меньше 1 (когда h стремится к бесконечности, сумма стремится к 1). В дальнейшем анализе давайте возьмем верхнюю границу суммы, которая равна 1.
Это дает S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
как h = log (n) , 2 h = n

Следовательно, S = n + log (n)
T (C) = O (n)

Танудж Ядав
источник
6

При построении кучи, допустим, вы подходите снизу вверх.

  1. Вы берете каждый элемент и сравниваете его с дочерними элементами, чтобы проверить, соответствует ли пара правилам кучи. Таким образом, листья включаются в кучу бесплатно. Это потому, что у них нет детей.
  2. При движении вверх наихудшим сценарием для узла прямо над листьями будет 1 сравнение (на максимуме они будут сравниваться только с одним поколением детей)
  3. Продвигаясь дальше, их непосредственных родителей можно максимально сравнить с двумя поколениями детей.
  4. Продолжая в том же направлении, вы получите сравнение log (n) для корня в худшем случае. и log (n) -1 для его непосредственных потомков, log (n) -2 для их непосредственных потомков и так далее.
  5. Подводя итоги, вы получите что-то вроде log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ..... + 1 * 2 ^ {( logn) -1}, который является ничем иным, как O (n).
Джонс
источник
2

В случае построения кучи, мы начинаем с высоты, logn -1 (где logn - высота дерева из n элементов). Для каждого элемента, присутствующего на высоте 'h', мы идем на максимум вверх (logn -h) вниз.

    So total number of traversal would be:-
    T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
    T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
    T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
     and according to the [sources][1]
    function in the bracket approaches to 2 at infinity.
    Hence T(n) ~ O(n)
Картик Гоял
источник
1

Последовательные вставки могут быть описаны:

T = O(log(1) + log(2) + .. + log(n)) = O(log(n!))

n! =~ O(n^(n + O(1)))Следовательно, по скворецу приближенияT =~ O(nlog(n))

Надеюсь, это поможет, оптимальный способ O(n)- использовать алгоритм кучи сборки для заданного набора (порядок не имеет значения).

Томер Шалев
источник
1

В основном, работа выполняется только на неконечных узлах при построении кучи ... и проделанная работа - это объем обмена, чтобы удовлетворить условию кучи ... другими словами (в худшем случае) это количество пропорционально высоте узла ... в целом сложность задачи пропорциональна сумме высот всех неконечных узлов .. которая равна (2 ^ h + 1 - 1) -h-1 = nh-1 = На)

Шубхам Джиндал
источник
1

@bcorso уже продемонстрировал доказательство анализа сложности. Но ради тех, кто все еще изучает анализ сложности, я хочу добавить следующее:

Основой вашей первоначальной ошибки является неправильное толкование значения выражения «вставка в кучу занимает O (log n) времени». Вставка в кучу - это действительно O (log n), но вы должны понимать, что n - это размер кучи во время вставки .

В контексте вставки n объектов в кучу сложность i-й вставки составляет O (log n_i), где n_i - размер кучи, как при вставке i. Только последняя вставка имеет сложность O (log n).

N.Vegeta
источник
1

Предположим, у вас есть N элементов в куче. Тогда его высота будет Log (N)

Теперь вы хотите , чтобы вставить еще один элемент, то сложность будет: Log (N) , мы должны сравнить весь путь UP к корню.

Теперь у вас есть N + 1 элементов и высота = Log (N + 1)

Используя технику индукции , можно доказать, что сложность введения была бы логической .

Сейчас использую

log a + log b = log ab

Это упрощает: ∑logi = log (n!)

что на самом деле O (NlogN)

Но

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

Это осознание пришло ко мне после подробного изучения экспериментов на кучах.

Fooo
источник
0

Мне очень нравится объяснение Джереми Уэста .... другой подход, который действительно легко понять, дан здесь http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity

поскольку buildheap зависит от использования, зависит от heapify и используется подход смещения, который зависит от суммы высот всех узлов. Итак, чтобы найти сумму высоты узлов, которая определяется как S = суммирование от i = 0 до i = h (2 ^ i * (hi)), где h = logn - высота дерева, решающего s, мы получаем s = 2 ^ (h + 1) - 1 - (h + 1), поскольку n = 2 ^ (h + 1) - 1 s = n - h - 1 = n-logn - 1 s = O (n), и поэтому сложность buildheap составляет O (n).

Нитиш джайн
источник
0

«Линейная временная граница сборки Heap, может быть показана путем вычисления суммы высот всех узлов в куче, которая является максимальным числом пунктирных линий. Для идеального двоичного дерева высоты h, содержащего N = 2 ^ ( h + 1) - 1 узлов, сумма высот узлов равна N - H - 1. Таким образом, это O (N). "

sec3
источник
0

Доказательство O (n)

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

Yi Y
источник
0

Мы получаем время выполнения для сборки кучи, вычисляя максимальное движение, которое может сделать каждый узел. Поэтому нам нужно знать, сколько узлов находится в каждой строке и как далеко от них может пройти каждый узел.

Начиная с корневого узла, каждая следующая строка имеет двойные узлы, чем предыдущая, поэтому, отвечая на то, как часто мы можем удваивать количество узлов, пока у нас не останется ни одного узла, мы получим высоту дерева. Или в математических терминах высота дерева равна log2 (n), где n - длина массива.

Чтобы вычислить узлы в одном ряду, мы начинаем сзади, мы знаем, что n / 2 узлов находятся внизу, поэтому, разделив на 2, мы получим предыдущий ряд и так далее.

Исходя из этого, мы получаем следующую формулу для подхода Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

Термин в последнем парантезе представляет собой высоту дерева, умноженную на один узел, находящийся в корне, термином в первом парантезе являются все узлы в нижнем ряду, умноженные на длину, которую они могут пройти, 0. Та же формула в смарте: введите описание изображения здесь

математический

Возвращая n обратно, мы имеем 2 * n, 2 можно отбросить, потому что его константа и тада, у нас наихудший случай выполнения подхода Siftdown: n.

Макс Тромп
источник
-6

Я думаю, что вы делаете ошибку. Взгляните на это: http://golang.org/pkg/container/heap/ Сборка кучи отсутствует O (n). Тем не менее, вставка - это O (lg (n). Я предполагаю, что инициализация - это O (n), если вы устанавливаете размер кучи b ​​/ c, куча должна выделять пространство и настраивать структуру данных. Если у вас есть n элементов для размещения тогда в кучу да, каждая вставка - это lg (n), и есть n элементов, так что вы получите n * lg (n), как указано выше

Майк Шахтер
источник
2
нет это не туго. более жесткий анализ кучи сборки дает O (n)
эмре невайширази
похоже, это оценка. Цитата в статье, на которую он ссылался, звучит так: «Интуиция заключается в том, что большинство вызовов heapify выполняются очень короткими кучами». Однако это делает некоторые предположения. Предположительно, для большой кучи в худшем случае все равно будет O (n * lg (n)), даже если обычно вы можете приблизиться к O (n). Но я могу ошибаться
Майк Шахтер
Да, это также мой интуитивный ответ, но ссылки, такие как википедия, заявляют: «Кучи с n элементами могут быть построены снизу вверх в O (n)».
GBa
1
Я думал о полностью отсортированной структуре данных. Я забыл конкретные свойства кучи.
Майк Шахтер