Купа проти бінарного дерева пошуку (BST)


169

Чим відрізняється купа і BST?

Коли використовувати купу і коли використовувати BST?

Якщо ви хочете отримати елементи сортованим способом, чи краще BST за грою?


13
Це питання , як видається, не по темі , тому що мова йде про комп'ютерній науці і має бути запропоновано на cs.stackexchange.com
Flow


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

Відповіді:


191

Підсумок

          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

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

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

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

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

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

    Це поширене хибне уявлення, тому що тривіально змінювати BST, щоб відслідковувати найбільший елемент, і оновлювати його, коли цей елемент можна було змінити: при вставці більшого заміну, при видаленні знайдіть другий за величиною. Чи можемо ми використовувати бінарне дерево пошуку для імітації операції купи? (згадував Єо ).

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

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

Джерела:

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

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

У двійковій купі збільшення значення при заданому індексі також відбувається O(1)з тієї ж причини. Але якщо ви хочете це зробити, цілком ймовірно, що вам захочеться постійно оновлювати додатковий індекс для операцій з купівлі. Як реалізувати операцію клавіші зменшення клавіш O (logn) для чергової черги на основі min-heap?наприклад для 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 Мб / с)

Так чітко:

  • Час вставлення купи в основному постійний.

    Ми можемо чітко бачити точки динамічного розміру масиву. Оскільки ми усереднюємо кожні 10k вставок щоб мати змогу побачити що-небудь вище, ніж системний шум , ці піки насправді приблизно в 10 к разів більше, ніж показано!

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

  • BST - логарифмічний. Усі вставки набагато повільніше, ніж середні вставки.

  • Детальний аналіз BST vs hashmap за адресою: Яка структура даних знаходиться в std :: map в C ++?

Стандартний орієнтир вставки бібліотеки 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 оновлює вузли кілька разів для кожного видалення, тож ми добре.

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

Купи можуть бути ефективно реалізовані на вершині купи покажчиків: Чи можна зробити ефективні реалізації бінарних куч на основі покажчика?

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

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

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

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

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

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

Філософія

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

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

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

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

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

Порівнюючи BST проти Heap та Hashmap:

  • BST: може бути або розумним:

    • не упорядкований набір (структура, яка визначає, чи був елемент раніше вставлений чи ні). Але хешмап, як правило, кращий завдяки амортизованій вставці O (1).
    • сортувальна машина. Але купа в цьому загалом краща, саме тому нагромадження набагато ширше відоме, ніж сорт дерева
  • купа: це лише сортувальна машина. Не може бути ефективним не упорядкований набір, оскільки ви можете швидко перевірити лише найменший / найбільший елемент.

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

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

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

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

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

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

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

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

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

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

Подібне запитання щодо CS: /cs/27860/whats-the-difference-bet between-a-binary-search-tree-and-a-binary- heap


4
Я + 1ed, але "папір", що виправдовує середню вставку бінарної купи O (1), тепер є мертвою ланкою, а "слайди" просто заявляють претензію без доказів. Крім того, я думаю, це допомогло б уточнити, що "середній випадок" тут означає середнє, якщо припустити, що вставлені значення походять від певного розподілу , тому я не впевнений, наскільки насправді "вбивча" ця функція.
j_random_hacker

3
BST і врівноважений BST, здається, використовуються без змін. Слід уточнити, що відповідь стосується збалансованого BST, щоб уникнути плутанини.
гкалпак

2
@Bulat Я відчуваю, що ми трохи відступаємо, але якщо ми хочемо одночасно і максимальну, і мінімальну хвилини, ми можемо зіткнутися з проблемами з підтриманням двох груп, якщо ми не будемо обережні - stackoverflow.com/a/1098454/7154924 . Напевно, краще використовувати максимум-міні-купу (завдяки Atkinson et al.), Яка спеціально розроблена для цієї мети.
flow2k

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功: Я не розумію, чому операція видалення бінарної купи O (log n). Це працює лише в тому випадку, якщо у вас є вказівник на елемент у купі, але в більшості випадків використання у вас є ключ, і вам потрібно спочатку знайти елемент, який приймає O (n).
Ricola

5
купа вставлення - це log (n) not o (1)
Бобо

78

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


8
"Купа просто гарантує, що елементи на більш високих рівнях є більшими (для max-heap) або меншими (для min-heap), ніж елементи на нижчих рівнях, ..." - купа не застосовує це за рівнем , а лише у батьків-child- ланцюги. [1, 5, 9, 7, 15, 10, 11]являє собою дійсну міні-купу, але рівень 7на рівні 3 менший, ніж 9на рівні 2. Для візуалізації див., наприклад, 25та 19елементи в зразку зображення Вікіпедії для купи . (Також зауважте, що відносини нерівності між елементами не є суворими, оскільки елементи необов'язково унікальні.)
Даніель Андерссон

Вибачте за пізній вхід, але я просто хочу отримати ясність. Якщо Binary Heap відсортований, то найгіршим випадком пошуку буде log n right. Тож у такому випадку сортуються Бінарні купи краще, ніж Двійкові пошукові дерева (Червоно-Чорні BST). Дякую
Крішна

50

Коли використовувати купу і коли використовувати BST

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

Спочатку кілька слайдів звідси пояснюють речі дуже чітко.


3
Хоча вставка є логарифмічною для обох у гіршому випадку, середня вставка купи займає постійний час. (Оскільки більшість існуючих елементів знаходяться внизу, в більшості випадків новий елемент повинен буде лише піднятися на один або два рівні, якщо вони є взагалі.)
johncip

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

2
@Yeo: Купа краще для findMin xor findMax. Якщо вам потрібно і те , і інше , тоді BST краще.
Mooing Duck

1
Я думаю, що це просто поширена помилка. Бінарне дерево можна легко змінити, щоб знайти min та max, як вказує Yeo. Це насправді обмеження купи: єдиним ефективним знахідкою є min або max. Істинне перевага купи O (1) середня вставка , як я пояснюю: stackoverflow.com/a/29548834/895245
Чіро Сантіллі郝海东冠状病六四事件法轮功

1
Відповідь Чіро Сантіллі є набагато краще: stackoverflow.com/a/29548834/2873507
Vic Seedoubleyew

9

Як зазначають інші, Heap може робити findMin або findMax в O (1), але не обидва в одній структурі даних. Однак я не погоджуюся з тим, що Heap краще в findMin / findMax. Насправді, з незначною модифікацією, BST може робити і те, findMin і findMax в O (1).

У цьому модифікованому BST ви відстежуєте мін та максимальний вузол кожного разу, коли ви робите операцію, яка може потенційно змінювати структуру даних. Наприклад, в операції вставки ви можете перевірити, чи мінімальне значення перевищує щойно введене значення, а потім призначити мінімальну величину щойно доданому вузлу. Ця ж техніка може бути застосована і до максимального значення. Отже, цей BST містить цю інформацію, яку ви можете отримати в O (1). (те саме, що і двійкова купа)

У цьому BST (збалансованому BST), коли ви pop minабо pop max, наступне значення min, яке буде призначено, є наступником min вузла, тоді як наступне максимальне значення, яке буде призначено, є попередником максимального вузла. Таким чином, вона виконується в O (1). Однак нам потрібно перебалансувати дерево, таким чином воно все одно буде працювати O (log n). (те саме, що і двійкова купа)

Мені було б цікаво почути вашу думку в коментарі нижче. Дякую :)

Оновлення

Перехресне посилання на подібне запитання Чи можемо ми використовувати бінарне дерево пошуку для імітації операції купи? для більш детальної дискусії щодо моделювання Heap за допомогою BST.


Чому ви не згодні? Ви б проти поділитися своєю думкою нижче?
Єо

Ви, безумовно, можете зберігати максимальне та / або мінімальне значення BST, але що робити, якщо ви хочете отримати його? Ви повинні шукати дерево, щоб видалити його, а потім знову знайти новий макс / хв, обидва з них - операції O (log n). Це той самий порядок, що і вставки та вилучення в пріоритетній купі, з гіршою константою.
Джастін

@JustinLardinois Вибачте, я забув виділити це у своїй відповіді. У BST, коли ви робите pop min, наступним мінімальним значенням, яке присвоюється, є наступник вузла min. і якщо ви виведете max, наступне значення max, яке буде призначено, є попередником max вузла. Таким чином, він все ще виступає в O (1).
Єо

Виправлення: бо popMinабо popMaxце не O (1), але це O (log n), тому що він повинен бути врівноваженим BST, який потрібно перебалансувати кожну операцію видалення. Значить, це те саме, що і двійкова купа popMinабо popMaxякі виконують O (log n)
Єо

2
Ви можете отримати перший min / max, але отримання kth min / max повернеться до нормальної складності BST.
Хаос

3

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

Де як купа, будучи реалізацією бінарного дерева, використовує таке визначення:

Якщо A і B - це вузли, де B - дочірній вузол A, то значення (ключ) від A повинно бути більшим або рівним значенню (ключу) B. Тобто, ключ (A) ≥ ключ (B ).

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

Сьогодні я побігав на те саме питання на іспиті, і я це правильно зрозумів. посміхніться ... :)


"купа, будучи реалізацією бінарного дерева" - лише вказуючи на те, що купа є своєрідним двійковим деревом, а не своєрідним BST
Саад

3

Ще одне використання BST over Heap; через важливу різницю:

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

Використання BST над купою : Скажімо, ми використовуємо структуру даних для зберігання посадкового часу польотів. Ми не можемо запланувати політ на посадку, якщо різниця у часі посадки менша ніж «d». І припустимо, що багато рейсів заплановано приземлитися в структурі даних (BST або Heap).

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

Відредаговано:

Сортування BST потребує часу O (n) для друку елементів у відсортованому порядку (Inorder traversal), тоді як Heap може робити це за O (n logn) час. Купа видобуває min елемент і повторно набирає масив, завдяки чому він здійснює сортування за O (n logn) часом.


1
Так. Це від несортованої до відсортованої послідовності. O (n) час для внутрішнього обходу BST, що дає відсортовану послідовність. Перебуваючи в Heaps, ви витягуєте min-елемент і потім повторно набираєте час O (log n). Отже, знадобиться O (n logn) для витягування n елементів. І це залишить вас відсортованою послідовністю.
CODError

from unsorted to sorted sequence. O(n) time for inorder traversal of a BST, which gives sorted sequence.Ну, від несортованої послідовності до BST, я не знаю методу, заснованого на порівнянні ключів із меншим часом O (n logn), який домінує над BST над частиною послідовності. (В той час як існує O (n) конструкція купи.) Я вважаю справедливим (якщо безглуздо), щоб державні купи були близькими до несортівності та відсортованих BST.
сіра борода

Я намагаюся пояснити тут, що якщо у вас є BST, а також купа n елементів =>, всі елементи можуть бути надруковані в упорядкованому порядку з обох структур даних, і BST може це зробити за O (n) час (Inverse traversal ), тоді як Heap зайняв би O (n logn) час. Я не розумію, що ви тут намагаєтеся сказати. How do you say BST дасть вам відсортовану послідовність у O (n logn).
CODError

Я думаю, ви також розглядаєте час, необхідний для створення BST та купи. Але я припускаю, що у вас це вже є, що ви створили його з часом, і тепер ви хочете отримати відсортований результат. Я не розумію вашої точки зору?
CODError

1
Відредаговано ... Я сподіваюся, що вас зараз влаштовує; p і дайте +1, якщо це правильно.
CODError

1

Вставлення всіх n елементів з масиву в BST займає O (n logn). n елементів у масиві можна вставити до купи за O (n) час. Що дає купі певну перевагу


0

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

Я люблю вищезазначену відповідь і ставлю свій коментар просто більш конкретним до моєї потреби та використання. Мені довелося змусити список списків n розташувати відстань від кожної локації до конкретної точки, скажімо, (0,0), а потім повернути ам-місця, що мають меншу відстань. Я використав пріоритетну чергу, яка є Heap. Для пошуку відстаней та розміщення в купі знадобилось n (log (n)) n-location log (n) кожне вставлення. Тоді для отримання m з найкоротшими відстанями знадобилося m (log (n)) m-location log (n) вилучення накопичення.

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

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