
Комментарии 62
А то так перетряхивает массив в процессе выполнения…
Мой комментарий был как раз про то, что неплохо бы увидеть эти примеры в статье.
А еще в STL также реализованы функции push_heap, pop_heap, make_heap и sort_heap.
Хорошая статья, однако неплохо бы добавить примеров применения binary heap, дабы у читателя не возникло ощущения что он слушает лекцию про абстрактные материи
Можно в очереди к врачу стоять в виде бинарной кучи.
public void buildHeap(int[] sourceArray)
{
list = sourceArray.ToList();
for (int i = /* !!! */ heapSize / 2; i >= 0; i--)
{
heapify(i);
}
}
Вы правы, я ошибся.
Метод heapify вызывается только для поддеревьев, состоящих более чем из одного элемента. То есть для деревьев высоты 2, 3,…, H (пусть H=log2N — высота дерева), причем поддеревьев высоты k всего есть 2H-k.
heapify «жрет» не более O(log2N), а на самом деле O(h), где h — высота поддерева, для которого heapify вызывается. Тогда итоговая сложность упорядочения будет равна (k пробегает по всем значениям высот поддеревьев, которые нас интересуют)

причем 2H — это количество вершин в дереве, то есть N. А сумма

не превосходит некоторой константы (хотите доказательство?). Значит, общее время работы алгоритмы все-таки O(N) :)
Не думал, что на хабре будут выкладывать стандартные алгоритмы/стрруктуры, которые можно найти в большенстве учебников/справочников.
Например вот эти статьи вам тоже не нравятся? А структуры данных описанные в них спокойно можно найти в Кормене/Кнуте или на емаксе.
«Фуууу, хабр уже не торт, расходимся пацаны !»
Действительно. Что делает техническая статья на развлекательном и новостном ресурсе?
Потомки гарантированно есть у первых heapSize/2 вершин.
for (int i = heapSize / 2; i >= 0; i--)
Мне кажется, или нужно сдвинуть верхнюю границу цикла?
for (int i = heapSize / 2 — 1; i >= 0; i--)
Дерево на первом рисунке имеет 10 вершин, т.о. потомки есть у первых пяти вершин, но индексы-то с нуля идут.
rain.ifmo.ru/cat/view.php/vis/heaps/bls-2006
====
Наиболее очевидный способ построить binary heap из неупорядоченного массива – это по очереди добавить все его элементы. Временная оценка такого алгоритма O(N log2 N).
====
Далее говорится о том, что при построении кучи методом обхода с конца массива (точнее, примерно с середины, по сути) сложность будет линейной. Вопрос. Чем эти 2 случая принципиально отличаются? При обходе с начала мы имеем те же N уровней высоты/глубины, но «просеивание» выполняем вверх, а не вниз, как при обходе с конца. Оба варианта «просеивания» имеют логарифмическую сложность. Кто-то может объяснить, в чем тут подвох? )
Но почему этого нельзя сделать для варианта «прямого» прохода по массиву? Ведь тут работает та же формула. Для каждого уровня глубины h от 0 до log2(n) мы выполняем для
того же количества узлов на каждом уровне глубины операцию «просеивания» вверх, которая в худшем случае выполняется за h обменов.
Пример на пальцах: если в дереве 15 вершин и используется оптимальный метод, то есть только одна вершина, которую (возможно) придется протолкнуть вниз на 3 уровня. На рисунке красная.
Если же добавлять вершины по одной, то есть 8 вершин, которые (возможно) придется проталкивать вверх на 3 уровня. Это зеленые вершины.

При использовании метода «прямого» обхода массива в худшем случае у нас будет 2 замены для узлов уровня глубины 1, 4 * 2 замены для узлов уровня глубины 2 и 8 * 3 замен для узлом самого нижнего уровня глубины — третьего. Итого 34.
Таким образом, вариант добавления узлов в начало дерево (с обходом с конца массива) более экономичен. Я правильно понял?
Не совсем понимаю данную строку:
Новый элемент добавляется на последнее место в массиве, то есть позицию с индексом
heapSize
Разве новый элемент будет добавляться не на позицию heapSize - 1 , а при обращении к элементу с индексом heapSize будет segfault, так как элемента с такми индексом еще нету. Если я не прав, попрошу исправить
Не знаю конечно, может я опять ошибаюсь, как-никак третий час ночи уже пошел), но в этой строке:
for (int i = heapSize / 2; i >= 0; i--) { heapify(i); }
должно быть не heapSize / 2, а heapSize / 2 - 1 , так как если мы не отнимаем один, то мы получаем индекс элемента не принадлежащего к определению того что Потомки гарантированно есть у данных элементов, объяснение(его подобие) на фото ниже:


Структуры данных: двоичная куча (binary heap)