Алгоритмы дерева на основе соединений - Join-based tree algorithms

В Информатика, древовидные алгоритмы на основе соединений представляют собой класс алгоритмов для самобалансирующиеся бинарные деревья поиска. Эта структура направлена ​​на разработку высокопараллельных алгоритмов для различных сбалансированных двоичных деревьев поиска. Алгоритмическая структура основана на одной операции присоединиться.[1] В рамках этой структуры присоединиться операция фиксирует все критерии балансировки различных схем балансировки и все другие функции присоединиться имеют общую реализацию в разных схемах балансировки. В основанные на соединениях алгоритмы может применяться как минимум к четырем схемам балансировки: Деревья АВЛ, красно-черные деревья, деревья со сбалансированным весом и Treaps.

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

История

В присоединиться операция была впервые определена Тарьяном [2] на красно-черные деревья, который выполняется в наихудшем логарифмическом времени. Позже Слеатор и Тарджан [3] описал присоединиться алгоритм для растопыренные деревья который работает в амортизированном логарифмическом времени. Позже Адамс [4] расширенный присоединиться к деревья со сбалансированным весом и использовал его для быстрых функций настройки, включая союз, пересечение и установить разницу. В 1998 году Blelloch и Reid-Miller расширили присоединиться на Treaps, и доказал, что оценка функций множества для двух деревьев размером и , что оптимально в сравнительной модели. Они также подняли параллелизм в алгоритме Адамса, используя схема "разделяй и властвуй". В 2016 году Blelloch et al. официально предложили алгоритмы, основанные на соединениях, и формализовали присоединиться алгоритм для четырех различных схем балансировки: Деревья АВЛ, красно-черные деревья, деревья со сбалансированным весом и Treaps. В той же работе они доказали, что алгоритмы Адамса на объединении, пересечении и разности оптимальны для работы на всех четырех схемах балансировки.

Алгоритмы соединения

Функция присоединиться учитывает перебалансировку дерева и, следовательно, зависит от схемы балансировки ввода. Если два дерева сбалансированы, присоединиться просто создает новый узел с левым поддеревом т1, корень k и правое поддерево т2. Предположим, что т1 тяжелее (это «тяжелее» зависит от схемы балансировки), чем т2 (другой случай - симметричный). Присоединиться следует за правым позвоночником т1 до узла c который сбалансирован с т2. На этом этапе новый узел с левым дочерним элементом c, корень k и правильный ребенок т2 создан для замены c. Новый узел может аннулировать инвариант балансировки. Это можно исправить поворотами.

Ниже приводится присоединиться алгоритмы на разных схемах балансировки.

В присоединиться алгоритм для деревьев AVL:

функция joinRightAVL (TL, k, Тр) (l, k ', c) = выставить (TL)    если (h (c) <= h (Tр) + 1) T '= Узел (c, k, Tр) если (h (T ') <= h (l) + 1), то вернуть Узел (l, k ', T') иначе вернуть rotateLeft (Узел (l, k ', rotateRight (T'))) еще         T '= joinRightAVL (c, k, Tр) Т = Узел (l, k ', T')        если (h (T ') <= h (l) + 1) вернуть Т        еще вернуть rotateLeft (T)функция joinLeftAVL (TL, k, Тр) / * симметрично joinRightAVL * /функция присоединиться (TL, k, Тр)    если (ч (тL)> h (Tр) + 1) вернуть joinRightAVL (TL, k, Тр)    если (ч (тр)> h (TL) + 1) вернуть joinLeftAVL (TL, k, Тр)    вернуть Узел (TL, k, Тр)

Вот узла высота . expose (v) = (l, k, r) означает извлечь узел дерева левый ребенок , ключ узла , и правильный ребенок . Узел (l, k, r) означает создание узла левого потомка , ключ , и правый ребенок .

В присоединиться алгоритм для красно-черных деревьев:

функция joinRightRB (TL, k, Тр)    если г (тL) = ⌊R (TL)/2⌋ × 2:        вернуть Узел (TL, ⟨K, красный⟩, Tр)    еще         (L ', ⟨k', c'⟩, R ') = выставить (TL) T '= Узел (L', ⟨k ', c'⟩, joinRightRB (R', k, Tр)        если (c '= черный) и (T'.right.color = T'.right.right.color = красный): T'.right.right.color = черный вернуть rotateLeft (T ') еще return T 'функция joinLeftRB (TL, k, Тр) / * симметрично joinRightRB * /функция присоединиться (TL, k, Тр)    если ⌊R (TL) / 2⌋> ⌊r (Tр) / 2⌋ × 2: T '= joinRightRB (TL, k, Тр)        если (T'.color = red) и (T'.right.color = red): T'.color = black return T ' иначе если ⌊R (TL) / 2⌋> ⌊r (TL) / 2⌋ × 2 / * симметричный * / иначе еслиL.color = черный) и (Tр = черный) Узел (TL, ⟨K, красный⟩, Tр)    еще        Узел (TL, ⟨K, черный⟩, Tр)

Вот узла означает удвоенную высоту черного черного узла и вдвое большую высоту черного красного узла. expose (v) = (l, ⟨k, c⟩, r) означает извлечь узел дерева левый ребенок , ключ узла , цвет узла и правильный ребенок . Узел (l, ⟨k, c⟩, r) означает создание узла левого потомка , ключ , цвет и правильный ребенок .

В присоединиться алгоритм для сбалансированных по весу деревьев:

функция joinRightWB (TL, k, Тр) (l, k ', c) = выставить (TL)    если баланс (| TL|, | ТL|) вернуть Узел (TL, k, Тр)    еще         Т '= joinRightWB (c, k, Tр) (l1, k1, р1) = выставить (T ') если (баланс (l, T ')) вернуть Узел (l, k ', T') иначе если (баланс (| l |, | l1|) и баланс (| l | + | l1|, | г1|))            вернуть rotateLeft (Узел (l, k ', T')) еще return rotateLeft (Узел (l, k ', rotateRight (T'))функция joinLeftWB (TL, k, Тр) / * симметрично joinRightWB * /функция присоединиться (TL, k, Тр)    если (тяжелый (TL, Тр)) вернуть joinRightWB (TL, k, Тр)    если (тяжелый (Tр, ТL)) вернуть joinLeftWB (TL, k, Тр) Узел (TL, k, Тр)

Здесь баланс означает два веса и сбалансированы. expose (v) = (l, k, r) означает извлечь узел дерева левый ребенок , ключ узла и правильный ребенок . Узел (l, k, r) означает создание узла левого потомка , ключ и правильный ребенок .

Алгоритмы на основе соединения

Далее expose (v) = (l, k, r) означает извлечение узла дерева. левый ребенок , ключ узла и правильный ребенок . Узел (l, k, r) означает создание узла левого потомка , ключ и правильный ребенок . правильно() и влево() извлекает правый дочерний элемент и левый дочерний элемент узла деревасоответственно. извлечь ключ узла . Многие алгоритмы на основе соединений параллельны. ""означает, что два утверждения и может работать параллельно.

Трещина

Чтобы разделить дерево на два дерева, меньшие, чем ключ Икс, и те, которые больше ключа Икс, мы сначала рисуем путь от корня, вставляя Икс в дерево. После этой вставки все значения меньше, чем Икс будут найдены слева от пути, и все значения больше чем Икс будет находиться справа. Применяя Присоединиться, все поддеревья на левой стороне объединяются снизу вверх с использованием ключей на пути в качестве промежуточных узлов снизу вверх, чтобы сформировать левое дерево, а правая часть асимметрична. Для некоторых приложений Трещина также возвращает логическое значение, обозначающее, если Икс появляется в дереве. Цена Трещина является , порядок высоты дерева.

Алгоритм разделения следующий:

функция разделить (T, k) если (T = nil) return (nil, false, nil) (L, m, R) = выставить (T) если (k = m) return (L, истина, R) если (k вернуть (L ', b, присоединиться (R', m, R)) если (k> m) (L ', b, R') = разделить (R, k) вернуть (присоединиться (L, m, L '), b, R'))

Присоединиться2

Эта функция определяется аналогично присоединиться но без средней клавиши. Сначала он отделяет последний ключ левого дерева, а затем соедините оставшуюся часть левого дерева с правым деревом с . Алгоритм следующий:

функция splitLast (T) (L, k, R) = выставить (T) если (R = ноль) вернуть (L, k) (T ', k') = splitLast (R) вернуть (присоединиться (L, k, T '), k')функция соединение2 (L, R) если (L = ноль) вернуть R (L ', k) = splitLast (L) вернуть присоединиться (L ', k, R)

Стоимость составляет для дерева размером .

Вставить и удалить

Алгоритмы вставки и удаления при использовании присоединиться может быть независимым от схем балансировки. Для вставки алгоритм сравнивает вставляемый ключ с ключом в корне, вставляет его в левое / правое поддерево, если ключ меньше / больше, чем ключ в корне, и соединяет два поддерева обратно с корнем. . При удалении удаляемый ключ сравнивается с ключом в корне. Если они равны, верните join2 для двух поддеревьев. В противном случае удалите ключ из соответствующего поддерева и снова присоедините два поддерева к корню. Алгоритмы следующие:

функция вставить (T, k) если (T = ноль) вернуть Узел (nil, k, nil) (L, k ', R) = expose (T) если (к <к ') вернуть присоединиться (вставить (L, k), k ', R) если (к> к ') вернуть присоединиться (L, k ', вставить (R, k)) вернуть Тфункция удалить (T, k) если (T = ноль) вернуть nil (L, k ', R) = выставить (T) если (к <к ') вернуть присоединиться (удалить (L, k), k ', R) если (к> к ') вернуть присоединиться (L, k ', удалить (R, k)) вернуть соединение2 (L, R)

И для вставки, и для удаления требуется время, если .

Установить – установить функции

Несколько операций над множеством были определены на деревьях с балансировкой веса: союз, пересечение и установить разницу. Объединение двух сбалансированных по весу деревьев т1 и т2 представляющие наборы А и B, это дерево т что представляет АB. Следующая рекурсивная функция вычисляет это объединение:

функция союз (т1, т2):    если т1 = ноль: вернуть т2    если т2 = ноль: вернуть т1<, б, т>) = разделить t2 на т1.root nl = union (left (t1), т<) || nr = union (right (t1), т>)    вернуть присоединиться (nl, t1.root, номер)

Аналогично, алгоритмы пересечения и разности множеств следующие:

функция пересечение (t1, т2):    если1 = ноль или t2 = ноль) вернуть ноль (т<, б, т>) = разделить t2 на т1.root nl = пересечение (left (t1), т<) || nr = пересечение (right (t1), т>)    если (б) вернуть присоединиться (nl, t1.root, номер) еще вернуть join2 (NL, NR)функция разница (т1, т2):    если1 = ноль) вернуть ноль если2 = ноль) вернуть т1<, б, т>) = разделить t2 на т1.root nl = разница (left (t1), т<) || nr = разница (справа (t1), т>)    вернуть join2 (NL, NR)

Сложность каждого из объединения, пересечения и различия равна для двух взвешенных по размеру деревьев и . Эта сложность оптимальна по количеству сравнений. Что еще более важно, поскольку рекурсивные вызовы объединения, пересечения или различия не зависят друг от друга, они могут быть выполнены в параллели с параллельная глубина .[1] Когда , реализация на основе соединения применяет те же вычисления, что и при вставке или удалении одного элемента, если корень большего дерева используется для разделения меньшего дерева.

Построить

Алгоритм построения дерева может использовать алгоритм объединения и схему «разделяй и властвуй»:

функция build (A [], n): если (п = 0) вернуть ноль если (n = 1) вернуть Узел (nil, A [0], nil) L = build (A, n / 2) || R = (A + n / 2, n-n / 2) вернуть союз (L, R)

Этот алгоритм стоит работа и имеет глубина. Более эффективный алгоритм использует алгоритм параллельной сортировки.

функция buildSorted (A [], n): если (п = 0) вернуть ноль если (n = 1) вернуть Узел (nil, A [0], nil) L = build (A, n / 2) || R = (А + п / 2 + 1, п-п / 2-1) вернуть присоединиться (L, A [n / 2], R)функция build (A [], n): A '= sort (A, n) вернуть buildSorted (A, n)

Этот алгоритм стоит работа и имеет глубина при условии, что алгоритм сортировки работа и глубина.

Фильтр

Эта функция выбирает все записи в дереве, удовлетворяющие индикатору , и вернуть дерево, содержащее все выбранные записи. Он рекурсивно фильтрует два поддерева и объединяет их с корнем, если корень удовлетворяет , в противном случае join2 два поддерева.

функция filter (T, f): если (T = ноль) вернуть nil L = filter (left (T), f) || R = (справа (T), f) если (f (k (T)) вернуть присоединиться (L, k (T), R) еще вернуть соединение2 (L, R)

Этот алгоритм стоит работы и глубина на дереве размера , предполагая имеет постоянную стоимость.

Используется в библиотеках

Алгоритмы на основе соединений применяются для поддержки интерфейса для наборы, карты, и дополненные карты [5] в библиотеках, таких как Взлом, SML / NJ, и PAM.[5]

Заметки

использованная литература

  1. ^ а б Blelloch, Guy E .; Феризович, Даниэль; Сунь, Ихан (2016), «Просто присоединяйтесь к параллельным упорядоченным множествам», Симпозиум по параллельным алгоритмам и архитектурам, Proc. 28-го симпозиума ACM. Параллельные алгоритмы и архитектуры (SPAA 2016), ACM, стр. 253–264, arXiv:1602.02120, Дои:10.1145/2935764.2935768, ISBN  978-1-4503-4210-0
  2. ^ Тарьян, Роберт Эндре (1983), «Структуры данных и сетевые алгоритмы», Структуры данных и сетевые алгоритмы, Siam, стр. 45–56.
  3. ^ Слеатор, Дэниел Доминик; Тарьян, Роберт Эндре (1985), «Самонастраивающиеся деревья двоичного поиска», Журнал ACM, Сиам
  4. ^ Адамс, Стивен (1992), «Эффективная реализация наборов на функциональном языке», Эффективная реализация наборов на функциональном языке, Citeseer, CiteSeerX  10.1.1.501.8427.
  5. ^ а б Blelloch, Guy E .; Феризович, Даниэль; Sun, Yihan (2018), "PAM: параллельные дополненные карты", Материалы 23-го симпозиума ACM SIGPLAN по принципам и практике параллельного программирования, ACM, стр. 290–304.

внешние ссылки

  • PAM, параллельная расширенная библиотека карт.
  • Взлом, Контейнеры в Hackage