Чим відрізняється купа і BST?
Коли використовувати купу і коли використовувати BST?
Якщо ви хочете отримати елементи сортованим способом, чи краще BST за грою?
Чим відрізняється купа і BST?
Коли використовувати купу і коли використовувати BST?
Якщо ви хочете отримати елементи сортованим способом, чи краще BST за грою?
Відповіді:
Підсумок
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)
амортизованого (сильнішого) типу кучки Фібоначчі , і навіть у гіршому випадку, як черга Бродала , хоча вони можуть бути не практичними через неасимптотичну продуктивність: Чи купи Фібоначчі або черди Бродаля на практиці використовуються де-небудь?
бінарні купи можна ефективно реалізовувати поверх будь-яких динамічних масивів або дерев на основі вказівника, лише BST-дерева. Таким чином, для купи ми можемо вибрати більш просторову реалізацію масиву, якщо ми можемо дозволити собі періодичні затримки для зміни розміру.
створення двійковій купи є O(n)
гіршим випадком , O(n log(n))
для BST.
Перевага BST над бінарною купою
пошук довільних елементів є O(log(n))
. Це вбивча особливість BST.
Для купи це O(n)
взагалі, за винятком найбільшого елемента, який є O(1)
.
"Хибна" перевага купи над BST
купи - O(1)
знайти макс., BSTO(log(n))
.
Це поширене хибне уявлення, тому що тривіально змінювати BST, щоб відслідковувати найбільший елемент, і оновлювати його, коли цей елемент можна було змінити: при вставці більшого заміну, при видаленні знайдіть другий за величиною. Чи можемо ми використовувати бінарне дерево пошуку для імітації операції купи? (згадував Єо ).
Власне, це обмеження купи в порівнянні з BST: єдиний ефективний пошук полягає в тому, що це найбільший елемент.
Середній розмір бінарної купи є O(1)
Джерела:
Інтуїтивний аргумент:
У двійковій купі збільшення значення при заданому індексі також відбувається O(1)
з тієї ж причини. Але якщо ви хочете це зробити, цілком ймовірно, що вам захочеться постійно оновлювати додатковий індекс для операцій з купівлі. Як реалізувати операцію клавіші зменшення клавіш O (logn) для чергової черги на основі min-heap?наприклад для Dijkstra. Можливо без додаткових витрат часу.
Стандартна бібліотека вставки GCC C ++ для реального обладнання
Я встановив орієнтацію на C ++ std::set
( червоно-чорне дерево BST ) та std::priority_queue
( динамічний масив масиву ), щоб побачити, чи я маю рацію щодо часу вставки, і ось що я отримав:
Так чітко:
Час вставлення купи в основному постійний.
Ми можемо чітко бачити точки динамічного розміру масиву. Оскільки ми усереднюємо кожні 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(n)
для обохВипадком для цього є тоді, коли ключем купи є поточна мітка часу: у такому випадку нові записи завжди перейдуть на початок списку. Тож ми можемо взагалі забути точну позначку часу і просто зберегти позицію в списку як пріоритетну.
Це можна використовувати для реалізації кешу LRU . Як і для купи програм, таких як Dijkstra , ви захочете зберегти додаткову хеш-карту від ключа до відповідного вузла списку, щоб знайти, який вузол швидко оновити.
Порівняння різних збалансованих BST
Хоча асимптотична вставка та час пошуку для всіх структур даних, які зазвичай класифікуються як "Збалансовані BSTs", які я бачив досі, є однаковим, різні BBST мають різні вигоди. Я ще цього не вивчив повністю, але було б добре підсумувати ці компроміси тут:
Дивитися також
Подібне запитання щодо CS: /cs/27860/whats-the-difference-bet between-a-binary-search-tree-and-a-binary- heap
Купа просто гарантує, що елементи на більш високих рівнях є більшими (для max-heap) або меншими (для min-heap), ніж елементи на нижчих рівнях, тоді як BST гарантує порядок (від "зліва" до "направо"). Якщо ви хочете відсортувати елементи, перейдіть з BST.
[1, 5, 9, 7, 15, 10, 11]
являє собою дійсну міні-купу, але рівень 7
на рівні 3 менший, ніж 9
на рівні 2. Для візуалізації див., наприклад, 25
та 19
елементи в зразку зображення Вікіпедії для купи . (Також зауважте, що відносини нерівності між елементами не є суворими, оскільки елементи необов'язково унікальні.)
Коли використовувати купу і коли використовувати BST
Купа краще знайти findMin / findMax ( O(1)
), тоді як BST хороший у всіх знахідках ( O(logN)
). Вставка призначена O(logN)
для обох структур. Якщо вас хвилює лише пошук FindMin / findMax (наприклад, пов'язаний з пріоритетом), перейдіть до купи. Якщо ви хочете, щоб все було відсортовано, перейдіть з BST.
Спочатку кілька слайдів звідси пояснюють речі дуже чітко.
Як зазначають інші, 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.
popMin
або popMax
це не O (1), але це O (log n), тому що він повинен бути врівноваженим BST, який потрібно перебалансувати кожну операцію видалення. Значить, це те саме, що і двійкова купа popMin
або popMax
які виконують O (log n)
Двійкове дерево пошуку використовує визначення: що для кожного вузла вузол зліва від нього має менше значення (ключ), а вузол праворуч від нього має більше значення (ключ).
Де як купа, будучи реалізацією бінарного дерева, використовує таке визначення:
Якщо A і B - це вузли, де B - дочірній вузол A, то значення (ключ) від A повинно бути більшим або рівним значенню (ключу) B. Тобто, ключ (A) ≥ ключ (B ).
http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree
Сьогодні я побігав на те саме питання на іспиті, і я це правильно зрозумів. посміхніться ... :)
Ще одне використання BST over Heap; через важливу різницю:
Використання BST над купою : Скажімо, ми використовуємо структуру даних для зберігання посадкового часу польотів. Ми не можемо запланувати політ на посадку, якщо різниця у часі посадки менша ніж «d». І припустимо, що багато рейсів заплановано приземлитися в структурі даних (BST або Heap).
Тепер ми хочемо запланувати ще один рейс, який приземлиться в t . Отже, нам потрібно обчислити різницю t з його наступником і попередником (повинна бути> d). Таким чином, нам знадобиться BST для цього, який робить це швидко, тобто в O (logn), якщо збалансований.
Відредаговано:
Сортування BST потребує часу O (n) для друку елементів у відсортованому порядку (Inorder traversal), тоді як Heap може робити це за O (n logn) час. Купа видобуває min елемент і повторно набирає масив, завдяки чому він здійснює сортування за O (n logn) часом.
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.
Купа просто гарантує, що елементи на більш високих рівнях є більшими (для 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 для купи.