Jump to content

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

В информатике алгоритмы деревьев на основе соединений представляют собой класс алгоритмов самобалансирующихся двоичных деревьев поиска . Целью этой структуры является разработка алгоритмов с высокой степенью параллелизма для различных сбалансированных деревьев двоичного поиска. Алгоритмическая основа основана на одной операции join . [1] В рамках этой структуры операция соединения фиксирует все критерии балансировки различных схем балансировки, а все остальные функции объединения имеют общую реализацию в разных схемах балансировки. Алгоритмы на основе соединений могут применяться как минимум к четырем схемам балансировки: деревьям AVL , красно-черным деревьям , деревьям со сбалансированным весом и трепам .

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

Операция соединения была впервые определена Тарьяном. [2] на красно-черных деревьях , который выполняется за логарифмическое время в худшем случае. Позже Слейтор и Тарьян [3] описал соединения алгоритм расширенных деревьев , который работает за амортизированное логарифмическое время. Позже Адамс [4] расширил соединение с деревьями со сбалансированным весом и использовал его для быстрых функций множества-множества, включая объединение , пересечение и разницу множеств . В 1998 году Блеллох и Рид-Миллер расширили соединение на Treaps и доказали, что граница функций множества равна для двух деревьев размером и , что является оптимальным в модели сравнения. Они также реализовали параллелизм в алгоритме Адамса, используя схему «разделяй и властвуй» . В 2016 году Блеллох и др. формально предложил алгоритмы на основе соединений и формализовал алгоритм соединения для четырех различных схем балансировки: деревьев AVL , красно-черных деревьев , деревьев со сбалансированным весом и трепов . В той же работе они доказали, что алгоритмы Адамса объединения, пересечения и разности оптимальны по работе для всех четырех схем балансировки.

Присоединяйтесь к алгоритмам

[ редактировать ]

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

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

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

function joinRightAVL(TL, k, TR)
    (l, k', c) := expose(TL)
    if h(c) ≤ h(TR) + 1
        T' := Node(c, k, TR)
        if h(T') ≤ h(l) + 1
            return Node(l, k', T')
        else
            return rotateLeft(Node(l, k', rotateRight(T')))
    else 
        T' := joinRightAVL(c, k, TR)
        T := Node(l, k', T')
        if h(T') ≤ h(l) + 1
            return T
        else
            return rotateLeft(T)

function joinLeftAVL(TL, k, TR)
    /* symmetric to joinRightAVL */

function join(TL, k, TR)
    if h(TL) > h(TR) + 1
        return joinRightAVL(TL, k, TR)
    else if h(TR) > h(TL) + 1
        return joinLeftAVL(TL, k, TR)
    else
        return Node(TL, k, TR)

Где:

  • высота узла .
  • извлекает левого дочернего элемента , ключ , и правый ребенок узла в кортеж .
  • создает узел с левым дочерним элементом , ключ , и правый ребенок .

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

function joinRightRB(TL, k, TR)
    if TL.color = black and ĥ(TL) = ĥ(TR)
        return Node(TL, ⟨k, red⟩, TR)
    else 
        (L', ⟨k', c'⟩, R') := expose(TL)
        T' := Node(L', ⟨k', c'⟩, joinRightRB(R', k, TR))
        if c' = black and T'.right.color = T'.right.right.color = red
            T'.right.right.color := black
            return rotateLeft(T')
        else
            return T'

function joinLeftRB(TL, k, TR)
    /* symmetric to joinRightRB */

function join(TL, k, TR)
    if ĥ(TL) > ĥ(TR)
        T' := joinRightRB(TL, k, TR)
        if (T'.color = red) and (T'.right.color = red)
            T'.color := black
        return T'
    else if ĥ(TR) > ĥ(TL)
        /* symmetric */
    else if TL.color = black and TR = black
        return Node(TL, ⟨k, red⟩, TR)
    else
        return Node(TL, ⟨k, black⟩, TR)

Где:

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

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

function joinRightWB(TL, k, TR)
    (l, k', c) := expose(TL)
    if w(TL) =α w(TR)
        return Node(TL, k, TR)
    else 
        T' := joinRightWB(c, k, TR)
        (l1, k1, r1) := expose(T')
        if w(l) =α w(T')
            return Node(l, k', T')
        else if w(l) =α w(l1) and w(l)+w(l1) =α w(r1)
            return rotateLeft(Node(l, k', T'))
        else
            return rotateLeft(Node(l, k', rotateRight(T'))

function joinLeftWB(TL, k, TR)
    /* symmetric to joinRightWB */

function join(TL, k, TR)
    if w(TL) >α w(TR)
        return joinRightWB(TL, k, TR)
    else if w(TR) >α w(TL)
        return joinLeftWB(TL, k, TR)
    else
        return Node(TL, k, TR)

Где:

  • это вес узла .
  • означает веса и являются α-весовыми сбалансированными.
  • означает вес тяжелее веса относительно α-весового баланса.
  • извлекает левого дочернего элемента , ключ , и правый ребенок узла в кортеж .
  • создает узел с левым дочерним элементом , ключ и правильный ребенок .

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

[ редактировать ]

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

Расколоть

[ редактировать ]

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

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

function split(T, k)
    if (T = nil)
        return (nil, false, nil)
    else
        (L, m, R) := expose(T)
        if k < m
            (L', b, R') := split(L, k)
            return (L', b, join(R', m, R))
        else if k > m
            (L', b, R') := split(R, k)
            return (join(L, m, L'), b, R'))
        else
            return (L, true, R)

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

[ редактировать ]

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

function splitLast(T)
    (L, k, R) := expose(T)
    if R = nil
        return (L, k)
    else
        (T', k') := splitLast(R)
        return (join(L, k, T'), k')

function join2(L, R)
    if L = nil
        return R
    else
        (L', k) := splitLast(L)
        return join(L', k, R)

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

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

[ редактировать ]

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

function insert(T, k)
    if T = nil
        return Node(nil, k, nil)
    else
        (L, k', R) := expose(T)
        if k < k'
            return join(insert(L,k), k', R)
        else if k > k'
            return join(L, k', insert(R, k))
        else
            return T

function delete(T, k)
    if T = nil
        return nil
    else
        (L, k', R) := expose(T)
        if k < k'
            return join(delete(L, k), k', R)
        else if k > k'
            return join(L, k', delete(R, k))
        else
            return join2(L, R)

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

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

[ редактировать ]

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

function union(t1, t2)
    if t1 = nil
        return t2
    else if t2 = nil
        return t1
    else
        (l1, k1, r1) := expose(t1)
        (t<, b, t>) := split(t2, k1)
        l' := union(l1, t<) || r' := union(r1, t>)
        return join(l', k1, r')

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

function intersection(t1, t2)
    if t1 = nil or t2 = nil
        return nil
    else
        (l1, k1, r1) := expose(t1)
        (t<, b, t>) = split(t2, k1)
        l' := intersection(l1, t<) || r' := intersection(r1, t>)
        if b
            return join(l', k1, r')
        else
            return join2(l', r')

function difference(t1, t2)
    if t1 = nil
         return nil
    else if t2 = nil
         return t1
    else
        (l1, k1, r1) := expose(t1)
        (t<, b, t>) := split(t2, k1)
        l' = difference(l1, t<) || r' = difference(r1, t>)
        if b
            return join2(l', r')
        else
            return join(l', k1, r')

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

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

function build(A[], n)
    if n = 0
        return nil
    else if n = 1
        return Node(nil, A[0], nil)
    else
        l' := build(A, n/2) || r' := (A+n/2, n-n/2)
        return union(L, R)

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

function buildSorted(A[], n)
    if n = 0
        return nil
    else if n = 1
        return Node(nil, A[0], nil)
    else
        l' := build(A, n/2) || r' := (A+n/2+1, n-n/2-1)
        return join(l', A[n/2], r')

function build(A[], n)
    A' := sort(A, n)
    return buildSorted(A, n)

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

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

function filter(T, p)
    if T = nil
        return nil
    else
        (l, k, r) := expose(T)
        l' := filter(l, p) || r' := filter(r, p)
        if p(k)
            return join(l', k, r')
        else
            return join2(l', R)

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

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

[ редактировать ]

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

Примечания

[ редактировать ]
  1. ^ Перейти обратно: а б Блеллок, Гай Э.; Феризович, Дэниел; Сунь, Ихан (2016), «Просто присоединяйтесь к параллельным упорядоченным наборам», Симпозиум по параллельным алгоритмам и архитектурам, Proc. 28-го симпозиума ACM. Параллельные алгоритмы и архитектуры (SPAA 2016) , ACM, стр. 253–264, arXiv : 1602.02120 , doi : 10.1145/2935764.2935768 , ISBN  978-1-4503-4210-0
  2. ^ Тарьян, Роберт Эндре (1983), «Структуры данных и сетевые алгоритмы», Структуры данных и сетевые алгоритмы , Сиам, стр. 45–56.
  3. ^ Слитор, Дэниел Доминик; Тарьян, Роберт Эндре (1985), «Самонастраивающиеся двоичные деревья поиска», Журнал ACM , Сиам
  4. ^ Адамс, Стивен (1992), «Эффективная реализация множеств на функциональном языке», Эффективная реализация множеств на функциональном языке , Citeseer, CiteSeerX   10.1.1.501.8427 .
  5. ^ Перейти обратно: а б Блеллок, Гай Э.; Феризович, Дэниел; Сунь, Ихан (2018), «PAM: параллельные дополненные карты», Материалы 23-го симпозиума ACM SIGPLAN по принципам и практике параллельного программирования , ACM, стр. 290–304
[ редактировать ]
  • PAM , параллельная расширенная библиотека карт.
  • Hackage , Контейнеры в Hackage
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 5e4669f1cd7530130ea66ac58af27c74__1713417840
URL1:https://arc.ask3.ru/arc/aa/5e/74/5e4669f1cd7530130ea66ac58af27c74.html
Заголовок, (Title) документа по адресу, URL1:
Join-based tree algorithms - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)