В информатике, сбалансированные по весу двоичные деревья (WBT ) - это тип самобалансирующихся двоичных деревьев поиска, которые можно использовать для реализации динамических устанавливает, словари (карты) и последовательности. Эти деревья были введены Нивергельтом и Рейнгольдом в 1970-х годах как деревья ограниченного баланса или BB [α] деревья . Их более распространенное название связано с Knuth.
Как и другие самобалансирующиеся деревья, WBT хранят бухгалтерскую информацию, относящуюся к балансу, в своих узлах и выполняют вращения для восстановления баланса, когда он нарушается вставкой или операции удаления. В частности, каждый узел хранит размер поддерева с корнем в этом узле, а размеры левого и правого поддеревьев сохраняются в пределах некоторого коэффициента друг от друга. В отличие от информации о балансе в деревьях AVL (которые хранят высоту поддеревьев) и красно-черных деревьях (которые хранят вымышленный бит "цвета"), бухгалтерская информация в WBT является действительно полезное свойство для приложений: количество элементов в дереве равно размеру его корня, а информация о размере - это в точности информация, необходимая для реализации операций дерева статистики порядка, а именно., получение n-го по величине элемента в наборе или определение индекса элемента в отсортированном порядке.
Весовые деревья популярны в сообществе функционального программирования и используются для реализации наборов и карты в схеме MIT, SLIB и реализациях Haskell.
Дерево со сбалансированным весом - это двоичное дерево поиска, в котором хранятся размеры поддеревьев в узлах. То есть узел имеет поля
По определению размер листа (обычно представленного нулевым указателем) равен нулю. Размер внутреннего узла - это сумма размеров двух его дочерних узлов плюс один (size [n] = size [n.left] + size [n.right] + 1). В зависимости от размера, вес определяется как вес [n] = размер [n] + 1.
Вращение двоичного дерева.Операции, которые изменяют дерево, должны гарантировать, что вес левого и правого поддеревья каждого узла остаются в пределах некоторого коэффициента α друг от друга, используя те же операции перебалансировки, которые используются в деревьях AVL: вращения и двойные вращения. Формально баланс узла определяется следующим образом:
Здесь α - числовой параметр, который должен быть определен при реализации деревьев со сбалансированным весом. Большие значения α создают «более сбалансированные» деревья, но не все значения α подходят; Нивергельт и Рейнгольд доказали, что
является необходимым условием для работы алгоритма балансировки. Более поздняя работа показала нижнюю границу ⁄ 11 для α, хотя ее можно сделать сколь угодно малой, если использовать собственный (и более сложный) алгоритм перебалансировки.
Правильное применение балансировки гарантирует дерево из n элементов будет иметь высоту
Количество операций балансировки, необходимых в последовательности из n вставок и удалений, линейно по n, т. е. балансировка требует постоянного количества накладных расходов в амортизированной sense.
При поддержании дерева с минимальной стоимостью поиска требуется четыре вида двойных вращений (LL, LR, RL, RR, как в дереве AVL) в операциях вставки / удаления, если мы хотим только логарифмической производительности, LR и RL - единственные повороты, необходимые для одного прохода сверху вниз.
Определено несколько операций набора на деревьях со сбалансированным весом: union, пересечение и устанавливает разницу. Затем на основе этих установленных функций могут быть реализованы быстрые массовые операции по вставке или удалению. Эти операции с наборами основаны на двух вспомогательных операциях: Split и Join. Благодаря новым операциям реализация сбалансированных по весу деревьев может быть более эффективной и высокопараллелизируемой.
. Алгоритм объединения выглядит следующим образом:
функция joinRightWB (T L, k, T R) (l, k ', c) = выставить (T L) ifбаланс (| T L |, | T L |) return Node (T L, k, T Relse T '= joinRightWB (c, k, T R) (l 1,k1, r 1) = выставить (T ') if (balance (l, T')) return Узел (l, k'T ') иначе, если (баланс (| l |, | l 1 |) и баланс (| l | + | l 1 |, | r 1 |)) return rotateLeft (Node (l, k ', T')) else return rotateLeft (Node (l, k ', rotateRight (T')) function joinLeftWB (T L, k, T R) / * симметрично joinRightWB * / function join (T L, k, T R) if(тяжелый (T L, T R)) return joinRightWB (T L, k, T R) if(тяжелый (T R, T L)) return joinLeftWB (T L, k, T R) Узел (T L, k, T R)
Здесь баланс означает два веса и сбалансированы. expose (v) = (l, k, r) означает извлечь левый дочерний элемент дерева , ключ узла и правого дочернего элемента . Узел (l, k, r) означает создание узла левого дочернего , ключа и правого child .
Алгоритм разделения выглядит следующим образом:
function split (T, k) if (T = nil) return (nil, false, nil) (L, (m, c), R) = expose (T) if (k = m) return (L, true, R) if (km) (L ', b, R') = split (R, k) return (join (L, m, L '), b, R))
Объединение два сбалансированных по весу дерева t 1 и t 2, представляющие множества A и B, представляют собой сбалансированное по весу дерево t, которое представляет A ∪ B. Следующая рекурсивная функция вычисляет это объединение:
функция union (t 1, t 2): ift1= nil: return t2ift2= nil: return t1t<, t>← разделить t 2 на t 1.root return join (union (left (t 1), t <), t 1.root, union (right (t 1), t>))
Здесь предполагается разделение t o вернуть два дерева: одно содержит ключи минус его вводной ключ, а второе - большие ключи. (Алгоритм является неразрушающим, но также существует деструктивная версия на месте.)
Алгоритм для пересечения или различия аналогичен, но требует вспомогательной подпрограммы Join2, которая является То же, что и Join, но без среднего ключа. На основе новых функций для объединения, пересечения или разницы, один или несколько ключей могут быть вставлены или удалены из сбалансированного по весу дерева. Поскольку Split и Union вызывают Join, но не имеют непосредственного отношения к критериям балансировки деревьев с балансировкой по весу, такая реализация обычно называется алгоритмами на основе соединения.
Сложность каждого из объединения, пересечения и разности составляет для двух весов -балансированные деревья размеров и . Эта сложность оптимальна по количеству сравнений. Что еще более важно, поскольку рекурсивные вызовы объединения, пересечения или разницы не зависят друг от друга, они могут выполняться параллельно с глубиной параллельности . Когда , реализация на основе соединения имеет тот же вычислительный направленный ациклический граф (DAG), что и вставка и удаление одиночных элементов, если корень большего дерева используется для разделения меньшего дерева.