Куча против бинарного дерева поиска (BST) - PullRequest
143 голосов
/ 27 мая 2011

В чем разница между кучей и BST?

Когда использовать кучу, а когда использовать BST?

Если вы хотите отсортировать элементы по порядку, лучше ли BST по сравнению с кучей?

Ответы [ 8 ]

154 голосов

Резюме

          Type      BST (*)   Heap
Insert    average   log(n)    1
Insert    worst     log(n)    log(n) or n (***)
Find any  worst     log(n)    n
Find max  worst     1 (**)    1
Create    worst     n log(n)  n
Delete    worst     log(n)    log(n)

Все средние значения времени в этой таблице совпадают с их худшими значениями времени, за исключением вставки.

  • *: везде в этом ответе BST == Сбалансированный BST, поскольку несбалансированный отстой асимптотически
  • **: использование тривиальной модификации, объясненной в этом ответе
  • ***: log(n) для кучи дерева указателей, n для кучи динамического массива

Преимущества двоичной кучи по сравнению с BST

Преимущество BST над двоичной кучей

  • поиск произвольных элементов O(log(n)). Это - убийственная особенность BST.

    Для кучи это O(n) в целом, за исключением самого большого элемента, который O(1).

«Ложное» преимущество кучи перед BST

  • куча O(1), чтобы найти максимум, BST O(log(n)).

    Это распространенное заблуждение, потому что тривиально модифицировать BST для отслеживания самого большого элемента и обновлять его всякий раз, когда этот элемент может быть изменен: при вставке большего свопа, при удалении найдите второе по величине. Можем ли мы использовать двоичное дерево поиска для имитации операции с кучей? (упоминается у Yeo ).

    На самом деле, это ограничение куч по сравнению с BST: эффективный поиск only - поиск самого большого элемента.

Средняя вставка двоичной кучи составляет O(1)

Источники:

Интуитивно понятный аргумент:

  • нижние уровни дерева имеют экспоненциально больше элементов, чем верхние уровни, поэтому новые элементы почти наверняка будут идти внизу
  • вставка кучи начинается снизу , BST должен начинаться сверху

В двоичной кучи увеличение значения по данному индексу также равно O(1) по той же причине. Но если вы хотите это сделать, вполне вероятно, что вы захотите поддерживать дополнительный индекс в актуальном состоянии для операций с кучей Как реализовать операцию уменьшения ключа O (logn) для приоритетной очереди на основе минимальной кучи? например для Дейкстры. Возможно без дополнительных затрат времени.

GCC C ++ эталонный тест вставки библиотеки на реальном оборудовании

Я протестировал вставку C ++ std::set ( Красно-черное дерево BST ) и std::priority_queue ( куча динамического массива ), чтобы убедиться, что я был прав насчет времени вставки, и вот что я получил:

enter image description here

  • контрольный код
  • сюжетный сценарий
  • данные графика
  • протестировано на Ubuntu 19.04, GCC 8.3.0 на ноутбуке Lenovo ThinkPad P51 с процессором: Процессор Intel Core i7-7820HQ (4 ядра / 8 потоков, база 2,90 ГГц, 8 МБ кэш-памяти), ОЗУ: 2x Samsung M471A2K43BB1-CRC (2x 16 ГБ, 2400 Мбит / с), твердотельный накопитель: Samsung MZVLB512HAJQ-000L7 (512 ГБ, 3000 МБ / с)

Так ясно:

  • Время вставки кучи в основном постоянное.

    Мы ясно видим точки изменения размера динамического массива. Поскольку каждые 10 тыс. Вставок мы усредняем, чтобы вообще видеть что-либо выше системного шума , эти пики на самом деле примерно в 10 тыс. Раз больше, чем показано!

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

  • BST является логарифмическим. Все вставки намного медленнее, чем вставка средней кучи.

  • BST против подробного анализа hashmap по адресу: Какая структура данных находится внутри std :: map в C ++?

GCC C ++ эталонный тест вставки библиотеки в gem5

gem5 - это симулятор полной системы, и поэтому обеспечивает бесконечно точные часы с m5 dumpstats. Поэтому я попытался использовать его для оценки времени для отдельных вставок.

enter image description here

Интерпретация:

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

    Это должно соответствовать задержкам доступа к памяти, которые выполняются для старших и старших вставок.

  • TODO Я не могу толковать BST полностью, поскольку он не выглядит настолько логарифмическим и несколько более постоянным.

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

С помощью этой настройки Buildroot на aarch64 ЦП HPI .

BST не может быть эффективно реализован в массиве

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

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

Кучи могут быть эффективно реализованы в массиве

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

Нет операций балансировки, таких как BST.

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

Если вы вставляете по одному узлу для каждого удаляемого, тогда вы теряете преимущество асимптотической средней (1) вставки, которую предоставляют кучки, так как удаление будет доминировать, и вы также можете использовать BST. Dijkstra, однако, обновляет узлы несколько раз для каждого удаления, так что мы в порядке.

Кучи динамических массивов и кучи дерева указателей

Кучи могут быть эффективно реализованы поверх кучи указателей: Можно ли сделать эффективные реализации двоичной кучи на основе указателей?

Реализация динамического массива более экономична. Предположим, что каждый элемент кучи содержит только указатель на struct:

  • реализация дерева должна хранить три указателя для каждого элемента: parent, left child и right child. Таким образом, использование памяти всегда 4n (3 указателя дерева + 1 struct указатель).

    Древовидным BST также потребуется дополнительная информация о балансировке, например, черно-красно-Несс.

  • реализация динамического массива может иметь размер 2n сразу после удвоения. Таким образом, в среднем это будет 1.5n.

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

Тем не менее, удвоение массива резервных копий O(1) амортизируется, поэтому сводится к рассмотрению максимальной задержки. Упоминается здесь .

Философия

  • BST поддерживают глобальное свойство между родителем и всеми потомками (слева меньше, справа больше).

    Верхний узел BST - это средний элемент, который требует глобальных знаний для поддержания (знания, сколько там мелких и больших элементов).

    Это глобальное свойство более дорогостоящее в обслуживании (регистрация n вставки), но дает более мощные поиски (регистрация n поиска).

  • Кучи поддерживают локальное свойство между родителем и прямым потомком (parent> children).

    Верхняя нота кучи - это большой элемент, который требует только местных знаний (зная вашего родителя).

Двусвязный список

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

  • вставка:
    • позиция:
      • двусвязный список: вставленный элемент должен быть первым или последним, поскольку у нас есть только указатели на эти элементы.
      • двоичная куча: вставленный элемент может оказаться в любой позиции. Менее ограниченный, чем связанный список.
    • время:
      • двусвязный список: O(1) худший случай, поскольку у нас есть указатели на элементы, а обновление действительно простое
      • двоичная куча: O(1) среднее значение, таким образом, хуже, чем связанный список. Компромисс для более общей позиции вставки.
  • поиск: O(n) для обоих

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

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

См. Также

Аналогичный вопрос по CS: https://cs.stackexchange.com/questions/27860/whats-the-difference-between-a-binary-search-tree-and-a-binary-heap

71 голосов
/ 27 мая 2011

Куча просто гарантирует, что элементы на более высоких уровнях больше (для максимальной кучи) или меньше (для минимальной кучи), чем элементы на более низких уровнях, тогда как BST гарантирует порядок (от «левого» до «правого»).Если вы хотите отсортированные элементы, используйте BST.

47 голосов
/ 04 июля 2013

Когда использовать кучу, а когда использовать BST

Куча лучше в findMin / findMax (O(1)), в то время как BST хорош в всех находках (O(logN)). Вставка O(logN) для обеих структур. Если вам нужна только программа findMin / findMax (например, связанная с приоритетом), используйте кучу. Если вы хотите, чтобы все было отсортировано, используйте BST.

Первые несколько слайдов из здесь объясняют вещи очень четко.

7 голосов
/ 22 ноября 2014

Как уже упоминалось, Heap может делать findMin или findMax в O (1), но не в обеих структурах данных.Однако я не согласен с тем, что Heap лучше в findMin / findMax.Фактически, с небольшой модификацией BST может делать и findMin и findMax в O (1).

В этом модифицированном BST вы отслеживаете узел min и узел max каждый раз, когда выполняете операцию, которая потенциально может изменить структуру данных.Например, в операции вставки вы можете проверить, больше ли минимальное значение, чем вновь вставленное значение, а затем назначить минимальное значение для вновь добавленного узла.Та же самая техника может быть применена к максимальному значению.Следовательно, этот BST содержит эту информацию, которую вы можете получить в O (1).(аналогично двоичной куче)

В этом BST (сбалансированном BST), когда вы pop min или pop max, следующее минимальное значение, которое будет назначено, является преемником минимального узлатогда как следующее максимальное значение, которое будет назначено, является предшественником максимального узла.Таким образом, он выполняет в O (1).Однако нам нужно перебалансировать дерево, поэтому оно все равно будет работать O (log n).(так же, как двоичная куча)

Мне было бы интересно услышать вашу мысль в комментарии ниже.Спасибо:)

Обновление

Перекрестная ссылка на аналогичный вопрос Можем ли мы использовать двоичное дерево поиска для имитации операции кучи? для более подробного обсуждения моделирования кучи с использованием BST.

3 голосов
/ 01 апреля 2015

Другое использование BST поверх Heap;из-за важного различия:

  • на поиск преемника и предшественника в BST потребуется O (h) время.(O (logn) в сбалансированном BST)
  • находясь в куче, потребуется O (n) время, чтобы найти преемника или предшественника какого-либо элемента.

Использование BSTчерез кучу : Теперь давайте скажем, что мы используем структуру данных для хранения времени посадки рейсов.Мы не можем запланировать полет на посадку, если разница во времени посадки меньше, чем «d».И предположим, что было запланировано много рейсов для посадки в структуре данных (BST или Heap).

Теперь мы хотим запланировать другой полет, который приземлится в t .Следовательно, нам нужно вычислить разницу t с ее преемником и предшественником (должно быть> d). Таким образом, для этого нам понадобится BST, который делает это быстро , т.е. в O (logn), если сбалансирован.

РЕДАКТИРОВАНО:

Сортировка BST требуется O (n) время для печати элементов в отсортированном порядке (обход Inorder), в то время как Heap может сделать это за O (n logn) время.Куча извлекает элемент min и повторно накапливает массив, что заставляет его выполнять сортировку за время O (n logn).

3 голосов
/ 25 июня 2013

Бинарное дерево поиска использует определение: для каждого узла узел слева от него имеет меньшее значение (ключ), а узел справа от него имеет большее значение (ключ).

Где в качестве кучи для реализации двоичного дерева используется следующее определение:

Если A и B - узлы, где B - дочерний узел A, то значение (ключ)) из A должно быть больше или равно значению (клавише) B. То есть клавиша (A) ≥ клавиша (B).

http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree

Я запускал втот же вопрос сегодня для моего экзамена, и я понял это правильно.улыбка ... :)

1 голос
/ 29 июня 2014

Вставка всех n элементов из массива в BST занимает O (n logn). n элементов в массиве можно вставить в кучу за O (n) раз. Что дает куче определенное преимущество

0 голосов
/ 01 октября 2017

Heap просто гарантирует, что элементы на более высоких уровнях больше (для max-heap) или меньше (для min-heap), чем элементы на более низких уровнях

Мне нравится приведенный выше ответ ипоместив мой комментарий только более конкретно для моей потребности и использования.Мне нужно было получить список из n местоположений, найти расстояние от каждого местоположения до определенной точки, скажем (0,0), а затем вернуть местоположения, имеющие меньшее расстояние.Я использовал Приоритетную очередь, которая является кучей.Для нахождения расстояний и помещения в кучу мне потребовалось n (log (n)) n-положений log (n) каждой вставки.Затем для получения m с наименьшими расстояниями потребовалось m (log (n)) m-положений log (n) удалений в кучу.

Я, если бы пришлось делать это с BST, мне потребовалось бы n (n) вставка в худшем случае. (Скажем, первое значение очень меньше, а все остальные идут последовательно все длиннее и длиннее, а дерево охватываеттолько правый ребенок или левый ребенок в случае все меньшего и меньшего. Минимум потребовалось бы за O (1) времени, но я снова должен был уравновесить. Поэтому из моей ситуации и всех приведенных выше ответов я получаю, когда вы только после значений вминимальная или максимальная приоритетная база идут для кучи.

...