Які додатки двійкових дерев?


323

Мені цікаво, які особливості застосування бінарних дерев. Чи можете ви навести кілька реальних прикладів?

Відповіді:


425

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

Застосування бінарних дерев

  • Бінарне дерево пошуку - використовується в багатьох пошукових програмах, де дані постійно вводяться / залишаються, наприклад, mapта setоб'єкти в бібліотеках багатьох мов.
  • Бінарний простір розділ - використовується майже в кожній 3D-грі для визначення, які об’єкти потрібно винести.
  • Бінарні проби - Використовуються майже в кожному маршрутизаторі високої пропускної здатності для зберігання маршрутизаційних таблиць.
  • Hash Trees - використовується в програмах p2p та спеціалізованих підписах зображень, у яких хеш потрібно перевірити, але весь файл недоступний.
  • Heaps - використовується для впровадження ефективних черг пріоритетів, які, в свою чергу, використовуються для планування процесів у багатьох операційних системах, якості якості обслуговування в маршрутизаторах та A * (алгоритм пошуку шляхів, що використовується в додатках AI, включаючи робототехніку та відеоігри) . Також використовується в купі сорту.
  • Дерево кодування Хаффмана ( Chip Uni ) - використовується в алгоритмах стиснення, таких як файли форматів .jpeg та .mp3.
  • GGM Trees - використовується в криптографічних додатках для генерування дерева псевдовипадкових чисел.
  • Синтаксичне дерево - Побудовано компіляторами та (неявно) калькуляторами для розбору виразів.
  • Treap - рандомізована структура даних, що використовується в бездротових мережах та розподілі пам'яті.
  • T-дерево - Хоча більшість баз даних використовують певну форму B-дерева для зберігання даних на накопичувачі, бази даних, які зберігають усі (більшість) своїх даних у пам'яті, часто використовують для цього T-дерева.

Причина того, що двійкові дерева використовуються частіше, ніж n-арі дерева для пошуку, полягає в тому, що n-ary-дерева є складнішими, але зазвичай не забезпечують реальної переваги швидкості.

У (врівноваженому) двійковому дереві з mвузлами для переміщення з одного рівня на інший потрібне одне порівняння, і є log_2(m)рівні для загального log_2(m)порівняння.

Навпаки, для n-ary-дерева потрібно буде log_2(n)порівняння (використовуючи двійковий пошук) для переходу на наступний рівень. Оскільки існують log_n(m)загальні рівні, пошук вимагатиме log_2(n)*log_n(m)= log_2(m)порівняння всього. Отже, хоча n-ary дерева є складнішими, вони не дають переваги в плані загального порівняння.

(Однак n-ary дерева все ще корисні в нішевих ситуаціях. Прикладами, які відразу приходять на думку, є квадратичні дерева та інші дерева, що розділяють простір, де поділ простору з використанням лише двох вузлів на рівні зробить логіку надмірно складною; і B-дерева, що використовуються у багатьох базах даних, де обмежуючим фактором є не кількість порівнянь на кожному рівні, а скільки вузлів можна завантажувати з жорсткого диска відразу)


3
> Treap - рандомізована структура даних, що використовується в бездротових мережах та розподілі пам'яті. Як саме вони використовуються для розподілу пам'яті та бездротових мереж?
frp

1
Існує маса корисних структур даних та алгоритмів, які використовують слово "бінарний", а "двійкове дерево ПОШУК" насправді є одним із них, але це не питання, яке було задано. Яку користь має звичайне старе "бінарне дерево", не сортоване, не збалансоване, не повне. Просто звичайне старе випадкове дерево?
Майкл Еріксон

4
@MichaelErickson Ви прочитали відповідь? Бо саме це питання я відповів.
BlueRaja - Danny Pflughoeft

1
Я вважаю, що хеш-дерева зазвичай називають Merkle Trees, принаймні, у спільнотах біткойн та ефіріум, IPFS тощо.
Герцог

1
Шкода, що ця відповідь містить стільки помилок. n-арі дерева сучасного обладнання майже завжди є переважними порівняно з бінарними деревами. Наведені програми здебільшого не використовують бінарні дерева.
Стефан Еггермонт

290

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

Неврівноважене дерево бінарного пошуку насправді корисне для навчання більше, ніж навчання учнів. Це тому, що, якщо дані не надходять у відносно випадковому порядку, дерево може легко перерости у найгірший вигляд, який є пов'язаним списком, оскільки прості бінарні дерева не врівноважені.

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

Alice
Bob
Chloe
David
Edwina
Frank

щоб, прочитавши його ще раз, опинилося наступне дерево:

  Alice
 /     \
=       Bob
       /   \
      =     Chloe
           /     \
          =       David
                 /     \
                =       Edwina
                       /      \
                      =        Frank
                              /     \
                             =       =

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

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

1.   Alice
    /     \
   =       =

 

2.   Alice
    /     \
   =       Bob
          /   \
         =     =

 

3.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       =

 

4.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       David
                    /     \
                   =       =

 

5.           Bob
        ____/   \____
   Alice             David
  /     \           /     \
 =       =     Chloe       Edwina
              /     \     /      \
             =       =   =        =

 

6.              Chloe
            ___/     \___
         Bob             Edwina
        /   \           /      \
   Alice     =      David        Frank
  /     \          /     \      /     \
 =       =        =       =    =       =

Насправді ви можете бачити цілі піддерева, що обертаються ліворуч (на кроках 3 та 6), коли записи додаються, і це дає вам збалансоване двійкове дерево, у якому найгірший випадок пошуку, O(log N)а не той O(N), який дає вироджена форма. Ні в якому разі найвищий NULL ( =) не відрізняється від найнижчого більш ніж на один рівень. І, в кінцевому дереві вище, ви можете знайти Франк лише дивлячись на трьох вузлах ( Chloe, Edwinaі, нарешті, Frank).

Звичайно, вони можуть стати ще кориснішими, якщо ви зробите їх збалансованими багатосторонніми деревами, а не двійковими тресами. Це означає, що кожен вузол містить більше одного елемента (технічно вони містять N елементів та покажчиків N + 1, двійкове дерево - особливий випадок одностороннього багатостороннього дерева з 1 елементом та 2 вказівниками).

З тристороннім деревом ви закінчите:

  Alice Bob Chloe
 /     |   |     \
=      =   =      David Edwina Frank
                 /     |      |     \
                =      =      =      =

Зазвичай це використовується для підтримки ключів для індексу елементів. Я написав програмне забезпечення для бази даних, оптимізоване для обладнання, де вузол має саме розмір дискового блоку (скажімо, 512 байт), і ви кладете стільки клавіш, скільки зможете в один вузол. Ці покажчики в даному випадку були фактично запис числа в фіксованої довжини, запис файлу прямого доступу окремо від індексного файлу (так номер запису Xможе бути знайдений тільки прагне X * record_length).

Наприклад, якщо покажчики складають 4 байти, а розмір ключа - 10, кількість клавіш у 512-байтовому вузлі - 36. Це 36 клавіш (360 байт) і 37 покажчиків (148 байт) на загальну суму 508 байт з 4 байти витрачено на вузол.

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

Я не бачу причин робити це для структури пам’яті, вам краще дотримуватися збалансованого бінарного дерева, а ваш код буде простий.

Також майте на увазі, що переваги O(log N)над O(N)справді не з’являються, коли ваші набори даних невеликі. Якщо ви використовуєте багатостороннє дерево для зберігання п’ятнадцяти людей у ​​своїй адресній книзі, це, ймовірно, зайве. Переваги приходять, коли ви зберігаєте щось на зразок кожного замовлення від своїх ста тисяч клієнтів протягом останніх десяти років.

Вся суть нотації big-O полягає в тому, щоб вказати на те, що відбувається в міру Nнаближення до нескінченності. Деякі люди можуть не погодитися, але навіть нормально використовувати сортування бульбашок, якщо ви впевнені, що набори даних залишаться нижче певного розміру, доки нічого іншого не буде легко :-)


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

  • Бінарні купи, де вищі клавіші вище або дорівнюють нижчим, а не ліворуч (або внизу, або врівень і вправо);
  • Дерева хешу, схожі на хеш-столи;
  • Абстрактні синтаксичні дерева для складання комп'ютерних мов;
  • Дерева Хаффмана для стиснення даних;
  • Маршрутизація дерев для мережевого трафіку.

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


28
+1 За таку ми написали відповідь; плюс познайомити мене з збалансованими багатосторонніми деревами, чогось я раніше не стикався.
Тоні

3
Я не погоджуюся з вашим твердженням, що вони корисні для мало іншого, крім навчання учнів. Вони є досить корисними навіть у вигляді простої статичної структури даних. Однак це дуже добре написана та ілюстрована відповідь, тому +1 для всіх інших. :-)
Бенсон

1
На сучасному обладнання майже всі дерева повинні бути багатосторонніми.
Стефан Еггермонт

89

Організація коду Морзе - це бінарне дерево.

двійкове дерево

морс-код


4
Це моя улюблена відповідь. Безпосередньо ілюструє зменшення складності обчислень, необхідних для досягнення символів далі за списком.
Вуса

7
Мені дуже сподобалась ця відповідь!
Дункан Едвардс

2
Раніше я працював у компанії, яка робила фільтри для радіостанцій, це відводить мене "назад" ...
ДжозефДоггі

62

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

Бінарне дерево

Двійкові дерева корисні, тому що, як ви бачите на малюнку, якщо ви хочете знайти який-небудь вузол на дереві, вам слід подивитися максимум 6 разів. Наприклад, якщо ви хотіли знайти вузол 24, ви б почали з кореня.

  • Корінь має значення 31, що більше 24, тому ви переходите до лівого вузла.
  • Лівий вузол має значення 15, що менше 24, тому ви переходите до правого вузла.
  • Правий вузол має значення 23, що менше 24, тому ви переходите до правого вузла.
  • Правий вузол має значення 27, що більше 24, тому ви переходите до лівого вузла.
  • Лівий вузол має значення 25, що більше 24, тому ви переходите до лівого вузла.
  • Вузол має значення 24, це ключ, який ми шукаємо.

Цей пошук показано нижче: Пошук дерев

Видно, що ви можете виключити половину вузлів усього дерева на першому проході. і половина лівого піддерев’я на другому. Це робить дуже ефективними пошуки. Якби це було зроблено на 4 мільярдах елементів, вам доведеться шукати максимум 32 рази. Тому, чим більше елементів міститься в дереві, тим ефективнішим може бути пошук.

Видалення може стати складним. Якщо у вузлі 0 або 1 дитина, тоді просто перенести деякі покажчики, щоб виключити той, який потрібно видалити. Однак ви не можете легко видалити вузол з двома дітьми. Отже, ми робимо скорочення. Скажімо, ми хотіли видалити вузол 19.

Видалити 1

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

Видалити 3

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

Видалити 4


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

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

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

У відсортованому масиві пошук все ще займеться O(log(n)), як і дерево, але випадкове вставлення та видалення займе O (n) замість дерева O(log(n)). Деякі контейнери STL використовують ці експлуатаційні характеристики на свою користь, тому час вставки та видалення займає максимум O(log n), що дуже швидко. Деякі з цих контейнерів map, multimap, set, і multiset.

Приклад коду дерева AVL можна знайти на веб- сайті http://ideone.com/MheW8


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

Вони не є типом, що зберігається у відповідних стандартних контейнерах.
Щеня

12

Основний додаток - двійкові дерева пошуку . Це структура даних, в якій пошук, вставлення та видалення все дуже швидко (про log(n)операції)


1
Двійкові дерева пошуку не є додатком, але є особливим типом бінарного дерева.
nbro

@nbro: Ви сперечаєтеся з безглуздою семантикою, це обидва дійсні способи сказати одне і те ж. Зауважте, що "додаток" тут не означає те саме, що "комп'ютерний додаток"
BlueRaja - Danny Pflughoeft

Я думаю, що питання стосувалося більше застосунків у реальному світі, а не конкретних реалізацій чи конкретних типів бінарних дерев. І btw, запитуючий не запитує, які структури даних є конкретними бінарними деревами. Це не безглуздо, ІМО. Але я згоден, що це все одно неоднозначно. Наприклад, у своїй іншій відповіді ви згадуєте синтаксичні дерева, що є додатком дерева (але не обов’язково бінарної ) структури даних у реальній програмі. Виходячи з ваших міркувань, тоді я міг би перерахувати всі бінарні дерева, які я знаю, всі ми були б раді через кількість предметів.
nbro


11

Один цікавий приклад бінарного дерева, про яке не згадували, - це рекурсивно оцінений математичний вираз. Це практично марно з практичної точки зору, але це цікавий спосіб думати про такі вирази.

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

Наприклад, вираз (1+3)*2можна виразити як:

    *
   / \
  +   2
 / \
1   3

Для оцінки виразу ми запитуємо значення батьківського значення. Цей вузол, у свою чергу, отримує свої значення від своїх дітей, оператора плюс та вузла, який просто містить «2». Оператор плюс, у свою чергу, отримує свої значення від дітей зі значеннями '1' та '3' і додає їх, повертаючи 4 у вузол множення, який повертає 8.

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



9

Я не думаю, що для "чистих" двійкових дерев немає користі. (крім освітніх цілей) Збалансовані двійкові дерева, такі як червоно-чорні дерева або дерева AVL, є набагато кориснішими, оскільки вони гарантують операції O (logn). Звичайні бінарні дерева можуть бути списком (або майже списком) і не дуже корисні для додатків, що використовують багато даних.

Збалансовані дерева часто використовуються для реалізації карт або наборів. Вони також можуть бути використані для сортування в O (nlogn), навіть там існують кращі способи зробити це.

Також можна шукати / вставляти / видаляти таблиці Hash , які зазвичай мають кращу продуктивність, ніж двійкові дерева пошуку (врівноважені чи ні).

Застосування, де (врівноважене) двійкові дерева пошуку було б корисним, якщо знадобиться пошук / вставка / видалення та сортування. Сортування може бути на місці (майже, ігноруючи простір стеку, необхідний для рекурсії), з огляду на готове дерево для збалансованого побудови. Це все ще буде O (nlogn), але з меншим постійним коефіцієнтом і не потребує додаткового простору (за винятком нового масиву, якщо припустити, що дані потрібно помістити в масив). Таблиці хешу з іншого боку не можна сортувати (принаймні, не безпосередньо).

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

Інші дерева, такі як дерева fe B + , широко використовуються в базах даних


9

Одне з найпоширеніших додатків - це ефективно зберігати дані у відсортованому вигляді для швидкого доступу та пошуку збережених елементів. Наприклад, std::mapабо std::setв стандартній бібліотеці C ++.

Бінарне дерево як структура даних є корисним для різних реалізацій аналізаторів вираження та вирішувачів виразів.

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

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


7

У C ++ STL та багатьох інших стандартних бібліотеках іншими мовами, як-от Java та C #. Двійкові дерева пошуку використовуються для реалізації набору та карти.


2
насправді в наборах / картах C ++ найчастіше базуються на червоно-чорних деревах, що є двійковим деревом пошуку з парою більше обмежень.
Ідан К

6

Одне з найважливіших застосувань бінарних дерев - це збалансовані дерева бінарного пошуку, такі як:

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

Завдяки цьому загальна висота деревних залишків порядку журналу n та такі операції, як пошук, вставлення та видалення вузлів, виконуються в O (log n) час. STL з C ++ також реалізує ці дерева у вигляді наборів і карт.


5

Їх можна використовувати як швидкий спосіб сортування даних. Вставте дані в двійкове дерево пошуку в O (log (n)). Потім обведіть дерева, щоб сортувати їх.


2

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



2

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


2
Субоптимальний порівняно з чим?

багатосторонні дерева. Лінійний пошук усіх даних, отриманих з одного доступу до пам'яті, набагато швидший, ніж новий доступ до основної пам’яті
Стефан Еггермонт

Я хочу вам вірити, але у вашій відповіді немає нічого, що підтверджує ваші претензії. Джерела, великі позначення чи щось таке. Будь ласка, докладно.
PhilT

0

Компілятор, який використовує двійкове дерево для представлення AST, може використовувати відомі алгоритми для розбору дерева, як postorder, inorder. Програмісту не потрібно придумувати власний алгоритм. Оскільки бінарне дерево для вихідного файлу вище, ніж n-ary дерево, його створення потребує більше часу. Візьміть це виробництво: selstmnt: = "if" "(" expr ")" stmnt "ELSE" stmnt У двійковому дереві він буде мати 3 рівні вузлів, але дерево n-ary матиме 1 рівень (з діток)

Ось чому ОС на базі Unix повільні.

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