Яка різниця між двійковим деревом пошуку та бінарною купою?


91

Ці два здаються дуже схожими і мають майже однакову структуру. Яка різниця? Які часові складності для різних операцій кожного?

Відповіді:


63

Купа просто гарантує, що елементи на більш високих рівнях є більшими (для max-heap) або меншими (для min-heap), ніж елементи на нижчих рівнях, тоді як BST гарантує порядок (від "зліва" до "направо"). Якщо ви хочете відсортувати елементи, перейдіть з BST. Данте - це не витівка

Купа краще знайти findMin / findMax (O (1)), тоді як BST хороший у всіх знахідках (O (logN)). Для обох структур Insert - O (logN). Якщо ви дбаєте лише про findMin / findMax (наприклад, пов’язаний з пріоритетом), перейдіть до купи. Якщо ви хочете, щоб все було відсортовано, перейдіть з BST.

від xysun


Я думаю , BST краще в findMin & findMax stackoverflow.com/a/27074221/764592
ЕО

10
Я думаю, що це просто поширена помилка. Бінарне дерево можна легко змінити, щоб знайти min та max, як вказує Yeo. Це фактично обмеження купи: єдиний ефективний висновок - це min або max. Істинне перевага купи O (1) середня вставка , як я пояснюю: stackoverflow.com/a/29548834/895245
Чіро Сантіллі新疆改造中心法轮功六四事件

Відповідно до цього відео , ви можете мати більші значення на нижчому рівні, доки більші не є нащадком нижнього.
whoan

Купа відсортована корінь до листя та BST сортується зліва направо.
Глибокий Джоші

34

І двійкові дерева пошуку, і бінарні купи - це структури даних на основі дерев.

Купи вимагають, щоб вузли мали пріоритет над своїми дітьми. У максимальній купі діти кожного вузла повинні бути меншими за себе. Це щонайменше навпаки для хвилини купи:

Бінарний Макс Хіп

Двійкові дерева пошуку (BST) дотримуються певного впорядкування (попереднє замовлення, замовлення, після замовлення) серед вузлів рідних братів. Дерево необхідно сортувати, на відміну від купи:

Двійкове дерево пошуку

BST мають середнє значення для вставки, видалення та пошуку. Бінарні Heaps мають середній для findMin / findMax та для вставки та видалення.O(logn)O ( 1 ) O ( log n )
O(1)O(logn)


1
@FrankW Видобуток , ні? O(logn)
flow2k

32

Підсумок

          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)

Усі середні часи цієї таблиці такі ж, як і їх найгірші часи, за винятком Insert.

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

Переваги бінарної купи над BST

  • середній час вставки в двійкову купу є O(1), для BST є O(log(n)). Це вбивча особливість купи.

    Існують також інші купи, які доходять до O(1)амортизованого (сильнішого) рівня, як фібоначчанська купа , і навіть у гіршому випадку, як черга Бродала , хоча вони можуть бути не практичними через неасимптотичну продуктивність: https://stackoverflow.com/questions/30782636 / are -jedno-heaps-or-brodal-queues-used-in-практиці будь-де

  • бінарні купи можна ефективно реалізовувати поверх будь-яких динамічних масивів або дерев на основі вказівника, лише BST-дерева. Тож для купи ми можемо вибрати більш просторову реалізацію масиву, якщо ми можемо дозволити собі періодичні затримки для зміни розміру.

  • створення двійковій купи є O(n)гіршим випадком , O(n log(n))для BST.

Перевага BST над бінарною купою

  • пошук довільних елементів є O(log(n)). Це вбивча особливість BST.

    Для купи це O(n)взагалі, за винятком найбільшого елемента, який є O(1).

"Хибна" перевага купи над BST

  • купи - O(1)знайти макс., BST O(log(n)).

    Це поширена помилкова думка, тому що тривіально змінювати BST, щоб відслідковувати найбільший елемент, і оновлювати його, коли цей елемент можна було змінити: при вставці більшого заміну, при видаленні знайдіть другий за величиною. https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulate-heap-operation (згаданий Єо ).

    Власне, це обмеження купи в порівнянні з BST: єдиний ефективний пошук полягає в тому, що це найбільший елемент.

Середній розмір бінарної купи є O(1)

Джерела:

Інтуїтивний аргумент:

  • У рівнях нижнього дерева є експоненціально більше елементів, ніж у верхньому, тому нові елементи майже впевнені, що йдуть у нижній частині
  • вставлення купи починається знизу , BST має починатися зверху

У бінарній купі збільшення значення при заданому індексі також відбувається O(1)з тієї ж причини. Але якщо ви хочете це зробити, цілком ймовірно, що вам захочеться оновлювати додатковий індекс для операцій по купі https://stackoverflow.com/questions/17009056/how-to-implement-ologn-decrease- черга чергових операцій на основі клавіш для мінімуму, наприклад, для Dijkstra. Можливо без додаткових витрат часу.

Стандартна бібліотека вставки GCC C ++ для реального обладнання

Я встановив орієнтацію на C ++ std::set( червоно-чорне дерево BST ) та std::priority_queue( динамічний масив масиву ), щоб побачити, чи я маю рацію щодо часу вставки, і ось що я отримав:

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

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

Так чітко:

Стандартний орієнтир вставки бібліотеки GCC C ++ на gem5

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

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

Інтерпретація:

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

    Це повинно відповідати затримкам доступу до пам’яті для більш високих і вищих вставок.

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

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

Цей параметр Buildroot відмічений на процесорі aarch64 HPI .

BST не може бути ефективно реалізований у масиві

Операції купи потребують лише міхура вгору або вниз по одній гілці дерева, тому O(log(n))найгірший випадок, O(1)середній.

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

Гропи можуть бути ефективно реалізовані на масиві

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

Таких операцій з балансування, як BST, немає.

Видалити хв - це сама тривожна операція, оскільки вона має бути зверху вниз. Але це завжди можна зробити, "просочивши" одну гілку купи, як пояснено тут . Це призводить до гіршого випадку O (log (n)), оскільки купа завжди добре збалансована.

Якщо ви вставляєте один вузол для кожного видаленого, ви втрачаєте перевагу середньої асимптотичної вставки O (1), яку купують, оскільки видалення буде домінувати, і ви також можете використовувати BST. Однак Dijkstra оновлює вузли кілька разів для кожного видалення, тож ми добре.

Динамічні купи масивів проти купи дерев вказівників

Купи можуть бути ефективно реалізовані на вершині купи покажчиків: https://stackoverflow.com/questions/19720438/is-it-possible-to-make-efficient-pointer-based-binary-heap-implementations

Реалізація динамічного масиву є більш просторовою. Припустимо, що кожен елемент купи містить лише вказівник на struct:

  • реалізація дерева повинна зберігати три покажчики на кожен елемент: батько, ліва дитина та права дитина. Тож використання пам'яті завжди 4n(3 вказівника на дерево + 1 structпокажчик).

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

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

З іншого боку, в купі дерев є кращі вставки, тому що копіювання динамічного масиву резервного копіювання для подвоєння його розміру має O(n)найгірший випадок, тоді як дерево дерев робить лише нові невеликі виділення для кожного вузла.

Проте подвоєння резервного масиву O(1)амортизується, тож воно зводиться до максимальної затримки. Тут згадується .

Філософія

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

    Верхній вузол BST - це середній елемент, який потребує глобальних знань для підтримання (знаючи, скільки там є менших і великих елементів).

    Ця глобальна властивість дорожче підтримувати (вставка n-вхід), але дає більш потужні пошуки (пошук n пошуку).

  • Купи підтримують місцеву властивість між батьками та прямими дітьми (батьки> діти).

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

Двозначно пов'язаний список

Подвійно пов'язаний список можна розглядати як підмножину купи, де перший елемент має найбільший пріоритет, тому давайте порівняємо їх і тут:

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

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

Це можна використовувати для реалізації кешу LRU . Як і для купи програм, таких як Dijkstra , ви захочете зберегти додаткову хеш-карту від ключа до відповідного вузла списку, щоб знайти, який вузол швидко оновити.

Порівняння різних збалансованих BST

Хоча асимптотична вставка та час пошуку для всіх структур даних, які зазвичай класифікуються як «Збалансовані BST», які я бачив досі, є однаковим, різні BBST мають різні вигоди. Я ще цього не вивчив повністю, але було б добре підсумувати ці компроміси тут:

  • Червоно-чорне дерево . Здається, що це найбільш часто використовуваний BBST станом на 2019 рік, наприклад, це той, який використовується впровадженням GCC 8.3.0 C ++.
  • AVL дерево . Схоже, трохи врівноваженіший, ніж BST, тому може бути кращим для пошуку затримок, ціною трохи дорожчих знахідок. Wiki підсумовує: "Дерева AVL часто порівнюють з червоно-чорними деревами, тому що обидва підтримують один і той же набір операцій і займають [однаковий] час для основних операцій. Для програм, що інтенсивно шукають, дерева AVL швидші, ніж червоно-чорні дерева, оскільки вони більш суворо збалансовані. Як і червоно-чорні дерева, дерева AVL мають рівновагу по висоті. Обидва вони, як правило, не збалансовані за вагою, ані му-збалансовані для будь-яких му <1/2; тобто вузли братів можуть мати дуже різна кількість нащадків ".
  • WAVL . У первинному документі згадуються переваги цієї версії в частині меж операцій по збалансування та обертання.

Дивитися також

Аналогічне запитання щодо CS: Яка різниця між бінарним деревом пошуку та бінарною купою?


1
Чудова відповідь. Поширене застосування купи - серединні, k min, верхні k елементи. Для цих найпоширеніших операцій видаліть min, а потім вставте (зазвичай у нас невелика купа з кількома чистими операціями вставки). Так здається на практиці, що для цих алгоритмів він не перевершує BST.
Юра

1
Виняткова відповідь !!! Використовуючи deque як основну структуру купи, ви можете різко зменшити розмір часу, хоча це все-таки найгірший випадок O (n), оскільки йому потрібно перерозподілити (менший) масив покажчиків на шматки.
Булат

13

У структурі даних слід відрізняти стурбованість.

  1. Ці абстрактні структури даних (об'єкти , що зберігаються, їх операція) в цьому питанні різні. Один реалізує чергу пріоритетів, інший - набір. Черга з пріоритетами не зацікавлена ​​у пошуку довільного елемента, лише того, який має найбільший пріоритет.

  2. Конкретна реалізація структур. Тут на перший погляд обидва (бінарні) дерева, але мають різні структурні властивості. Як відносне впорядкування ключів, так і можливі глобальні структури відрізняються. (Дещо неточно, в BSTклавішах упорядковано зліва направо, в купі вони впорядковані зверху вниз.) Оскільки IPlant правильно зауважує, купа також повинна бути "повною".

  3. Існує остаточна різниця в реалізації низького рівня . (Незбалансоване) двійкове дерево пошуку має стандартну реалізацію за допомогою покажчиків. Бінарна купа, навпаки, має ефективну реалізацію з використанням масиву (саме через обмежену структуру).


1

Крім попередніх відповідей, купа повинна мати властивість структури купи; дерево повинно бути заповненим, а самий нижній шар, який не завжди може бути заповненим, повинен бути заповнений зліва вправо та без правої щілини.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.