Чи допускаються повторювані ключі у визначенні дерев бінарного пошуку?


139

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

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

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

А в моїй старій книзі про структуру даних коледжу сказано, що "кожен елемент має ключ, і жоден два елементи не мають однакового ключа".

Чи існує універсальне визначення bst? Зокрема, стосовно того, що робити з деревами з кількома екземплярами одного ключа.

EDIT: Можливо, мені було незрозуміло, визначення, які я бачу, є

1) зліва <= корінь <праворуч

2) лівий <корінь <= правий

3) ліворуч <root <право, таким чином, що не існує дублікатів ключів.

Відповіді:


78

Багато алгоритмів визначають, що дублікати виключені. Наприклад, алгоритми прикладу в книзі Алгоритмів MIT зазвичай представляють приклади без дублікатів. Реалізація дублікатів досить тривіальна (або як список у вузлі, або в одному конкретному напрямку.)

Більшість (що я бачив) визначають лівих дітей як <=, а правих дітей як>. Практично кажучи, для BST, який дозволяє правим або лівим дітям бути рівним кореневому вузлу, знадобляться додаткові обчислювальні кроки для завершення пошуку, де дозволені повторювані вузли.

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

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

Тож почніть з тим, що сказано у вашій книзі структур даних!

Редагувати:

Універсальне визначення дерева бінарного пошуку включає збереження та пошук ключа, заснованого на переході структури даних в одному з двох напрямків. У прагматичному сенсі це означає, що якщо значення <>, ви переходите структуру даних в одному з двох "напрямків". Отже, у цьому сенсі дублюючі значення взагалі не мають жодного сенсу.

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


1
Які недоліки використання списку на вузлі?
Pacerier

1
@Pacerier Я думаю, що замість того, щоб підтримувати список, ми можемо підтримувати кількість посилань на кожному вузлі та оновлювати кількість, коли виникає дублікат. Такий алгоритм був би набагато простішим та ефективнішим у пошуку та зберіганні. Крім того, знадобиться мінімальна зміна існуючого алгоритму, який не підтримує дублікатів.
SimpleGuy

50

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

зліва <корінь <= праворуч

А тепер уявіть просте дерево, корінь якого 5, ліворуч - нульове, а праворуч - 5. Якщо ви робите ліве обертання на корені, ви отримуєте 5 у лівій дитині та 5 у корені у правої дитини будучи нульовим. Тепер щось у лівому дереві дорівнює кореню, але ваше правило вище припускає лівий <корінь.

Я витрачав години, намагаючись з’ясувати, чому мої червоні / чорні дерева час від часу виходять з ладу, проблема була в тому, що я описав вище. Сподіваємось, хтось читає це і економить собі години налагодження в майбутньому!


18
Не обертайтесь, коли у вас рівні вузли! Перейдіть до наступного рівня та оберніть це.
Багатий

2
Інші рішення - або змінити дерево дерево, яке буде left <= node <= right, або лише вставити до першого появи значення.
paxdiablo

Які проблеми це може викликати на практиці? Мені здається, що якщо ви добре з лівим <= вузлом <= правою, то всі операції з червоно-чорним деревом все одно спрацюють.
Бьорн Ліндквіст

39

Всі три визначення прийнятні та правильні. Вони визначають різні варіанти BST.

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

Безумовно, додання дублікатів додає складності. Якщо ви використовуєте визначення "left <= root <right" і у вас є дерево типу:

      3
    /   \
  2       4

то додавання дублюючого ключа "3" до цього дерева призведе до:

      3
    /   \
  2       4
    \
     3

Зауважте, що дублікати не перебувають у суміжних рівнях.

Це велика проблема, коли дозволити дублікати в BST-представленні, як зазначене вище: дублікати можуть бути розділені на будь-яку кількість рівнів, тому перевірити наявність дубліката не так просто, як просто перевірити наявність безпосередніх дочірніх вузлів.

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

      3(1)
    /     \
  2(1)     4(1)

а після введення дубліката ключа "3" він стане:

      3(2)
    /     \
  2(1)     4(1)

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


Я дуже здивований, що про це ніколи навіть не згадували в підручнику, який я використовую. Професор також не згадав про це, ні про те, що дублікати ключів є навіть проблемою smh ...
Oloff Biermann

22

У BST всі значення, що спадають на лівій частині вузла, менші (або рівні, див. Пізніше) самого вузла. Аналогічно, всі значення, що спадають на праву частину вузла, більше (або рівні) значення вузлів (a) .

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

Наступний приклад може уточнити:

            |
      +--- 14 ---+
      |          |
+--- 13    +--- 22 ---+
|          |          |
1         16    +--- 29 ---+
                |          |
               28         29

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

Це можна зробити рекурсивно з чимось на зразок:

def hasVal (node, srchval):
    if node == NULL:
         return false
    if node.val == srchval:
        return true
    if node.val > srchval:
        return hasVal (node.left, srchval)
    return hasVal (node.right, srchval)

і називати це за допомогою:

foundIt = hasVal (rootNode, valToLookFor)

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


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


Що стосується дублюючого випадку, ви можете просто перевірити, чи потрібна дитина дорівнює поточному вузлу в пункті node.val == srchval:, а потім, якщо так, правильно?
bneil

9

У книзі "Вступ до алгоритмів", третє видання, Кормен, Лейзерсон, Рівест та Штейн, двійкове дерево пошуку (BST) чітко визначене як дозвіл дублікатів . Це можна побачити на малюнку 12.1 та наступному (стор. 287):

"Ключі в дереві двійкового пошуку завжди зберігаються таким чином, щоб задовольнити властивість дерева бінарного пошуку: Нехай xбуде вузол у дереві двійкового пошуку. Якщо yце вузол у лівому піддереві x, тоді y:key <= x:key. Якщо yце то, вузол у правому піддереві x, тоді y:key >= x:key. "

Крім того, на сторінці 308 визначається червоно-чорне дерево як:

"Червоно-чорне дерево - це бінарне дерево пошуку з одним додатковим бітом зберігання на вузол: його колір"

Тому червоно-чорні дерева, визначені в цій книзі, підтримують дублікати.


4

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


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

1
Також зауважте із вікі, що праве піддерево містить значення кореня «більше або рівне». Отже, визначення вікі є суперечливим.
SoapBox

1
+1: різні люди використовують різні визначення. Якщо ви впроваджуєте нову BST, вам потрібно переконатися, що ви чітко розумієте, які припущення ви робите щодо дублюючих записів.
Містер Фооз

1
Здається, що консенсус є (лівий <= корінь <= правий), коли дозволено дублікати. Але те, що дефініція BST не дозволяє людям. А може, деякі люди навчають його саме тому, щоб уникнути додаткової складності.
Тім Мерріфілд

1
неправильно! це ВСЕ ліво <= корінь <право АБО ліво <корінь <= право, АБО ліво> корінь> = право АБО ліво> = корінь> праворуч
Мітч пшениця

3

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

left <= root <= right

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


2

Ці три речі, про які ви сказали, всі правдиві.

  • Ключі унікальні
  • Зліва - клавіші менше, ніж ця
  • Праворуч - клавіші, більші за цю

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


1

1.) зліва <= корінь <праворуч

2.) зліва <корінь <= праворуч

3.) зліва <root <право, таким чином, що не існує дублікатів ключів.

Можливо, мені доведеться піти і викопати свої книги з алгоритмами, але вгорі голови (3) це канонічна форма.

(1) або (2) виникають лише тоді, коли ви починаєте дозволити вузли дублікатів і ви ставите дублікати вузлів у самому дереві (а не вузол, що містить список).


Чи можете ви пояснити, чому лівий <= корінець <= правий не ідеальний?
Хелін Ван

Подивіться на прийняту відповідь від @paxdiablo - Ви можете бачити, що значення дубліката може існувати за допомогою >=. Ідеал залежить від ваших вимог, але якщо у вас багато дублікатів, і ви дозволяєте дублікатам існувати в структурі, ваш bst може в кінцевому підсумку бути лінійним - тобто O (n).
Роберт Поулсон

1

Дублікати клавіш • Що станеться, якщо є декілька даних даних з одним ключем? - Це представляє невелику проблему в червоно-чорних деревах. - Важливо, щоб вузли з одним ключем були розподілені по обидва боки інших вузлів тим самим ключем. - Тобто, якщо ключі надходять у порядку 50, 50, 50, • ви хочете, щоб другий 50 йшов праворуч від першого, а третій - 50 ліворуч від першого. • В іншому випадку дерево стає незбалансованим. • Це може вирішити якийсь процес рандомізації в алгоритмі вставки. - Однак процес пошуку потім ускладнюється, якщо потрібно знайти всі елементи з одним ключем. • Простіше заборонити предмети одним ключем. - У цьому обговоренні ми припустимо, що копії заборонені

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


1

Я просто хочу додати ще трохи інформації до того, на що відповів @Robert Paulson.

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

1) зліва <= cur <праворуч

2) зліва <cur <= праворуч

3) зліва <= cur <= праворуч

4) ліворуч <cur <право && cur містять вузли братів з тією ж клавішею.

5) ліворуч <cur <право, таким чином, що не існує дублікатів ключів.

1) і 2) добре працює, якщо дерево не має жодних функцій, пов'язаних з обертанням, щоб запобігти косості.
Але ця форма не працює з AVL-деревом або червоно-чорним деревом , оскільки обертання порушить головну.
І навіть якщо search () знайде вузол з ключем, він повинен просуватися до вузла аркуша для вузлів з повторюваним ключем.
Складання складності в часі для пошуку = theta (logN)

3) буде добре працювати з будь-якою формою BST з функціями, пов'язаними з обертанням.
Але пошук займе O (n) , зруйнуючи мету використання BST.
Скажіть, у нас є дерево, як показано нижче, з 3) головним.

         12
       /    \
     10     20
    /  \    /
   9   11  12 
      /      \
    10       12

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

4) мій особистий фаворит. Скажімо, побратим означає вузол з тим самим ключем.
Ми можемо змінити верхнє дерево на нижнє.

         12 - 12 - 12
       /    \
10 - 10     20
    /  \    /
   9   11  12

Тепер будь-який пошук займе O (logN), оскільки нам не доведеться перебирати дітей за дублюючим ключем.
І цей головний також добре працює з AVL або RB деревом .


0

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

В іншому випадку, якщо є дублікати, вам потрібно запустити двічі або більше одну і ту ж функцію видалення!

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