Левосторонние красно-чёрные деревья — различия между версиями
Строка 6: | Строка 6: | ||
==Вращения== | ==Вращения== | ||
Чтобы поддерживать левосторонние красно-черные двоичные деревья поиска необходимо соблюдать следующие инварианты при вставке и удалении: | Чтобы поддерживать левосторонние красно-черные двоичные деревья поиска необходимо соблюдать следующие инварианты при вставке и удалении: | ||
− | * Ни один | + | * Ни один путь от корня до листьев дерева не содержит двух последовательных красных узлов. |
* Количество черных узлов на каждом таком пути одинаково. | * Количество черных узлов на каждом таком пути одинаково. | ||
Из этих инвариантов следует, что длина каждого пути от корня до листьев в красно-черном дереве с <tex>N</tex> узлами не превышает <tex>2 \cdot \log(N)</tex> . | Из этих инвариантов следует, что длина каждого пути от корня до листьев в красно-черном дереве с <tex>N</tex> узлами не превышает <tex>2 \cdot \log(N)</tex> . | ||
Строка 74: | Строка 74: | ||
'''Node''' insert(h : '''Node''', key : '''Key''', value : '''Value''') | '''Node''' insert(h : '''Node''', key : '''Key''', value : '''Value''') | ||
− | <span style="color:#008000">//Вставка нового узла к листу дерева</span> | + | <span style="color:#008000">// Вставка нового узла к листу дерева</span> |
'''if''' (h == ''null'') | '''if''' (h == ''null'') | ||
'''return''' '''new''' Node(key, value) | '''return''' '''new''' Node(key, value) | ||
− | <span style="color:#008000">//Расщепление узла с <tex>4</tex>-я потомками</span> | + | <span style="color:#008000">// Расщепление узла с <tex>4</tex>-я потомками</span> |
'''if''' (isRed(h.left) '''&&''' isRed(h.right)) | '''if''' (isRed(h.left) '''&&''' isRed(h.right)) | ||
colorFlip(h) | colorFlip(h) | ||
− | <span style="color:#008000">//Стандартная вставка [[Дерево поиска, наивная реализация|в дереве поиска]]</span> | + | <span style="color:#008000">// Стандартная вставка [[Дерево поиска, наивная реализация|в дереве поиска]]</span> |
'''int''' cmp = key.compareTo(h.key) | '''int''' cmp = key.compareTo(h.key) | ||
'''if''' (cmp == 0) | '''if''' (cmp == 0) | ||
Строка 89: | Строка 89: | ||
'''else''' | '''else''' | ||
h.right = insert(h.right, key, value) | h.right = insert(h.right, key, value) | ||
− | <span style="color:#008000">//Принудительное вращение влево</span> | + | <span style="color:#008000">// Принудительное вращение влево</span> |
'''if''' (isRed(h.right) '''&&''' '''!'''isRed(h.left)) | '''if''' (isRed(h.right) '''&&''' '''!'''isRed(h.left)) | ||
h = rotateLeft(h) | h = rotateLeft(h) | ||
− | <span style="color:#008000">//Балансировка узла с <tex>4</tex>-я потомками</span> | + | <span style="color:#008000">// Балансировка узла с <tex>4</tex>-я потомками</span> |
'''if''' (isRed(h.left) '''&&''' isRed(h.left.left)) | '''if''' (isRed(h.left) '''&&''' isRed(h.left.left)) | ||
h = rotateRight(h) | h = rotateRight(h) | ||
Строка 119: | Строка 119: | ||
*Использование Переворота цветов и вращений сохраняет баланс черной связи. | *Использование Переворота цветов и вращений сохраняет баланс черной связи. | ||
*После удаления необходимо исправить правые красные связи и устранить узлы с <tex>4</tex>-я потомками | *После удаления необходимо исправить правые красные связи и устранить узлы с <tex>4</tex>-я потомками | ||
− | <span style="color:#008000">//Исправление правых красных связей</span> | + | <span style="color:#008000">// Исправление правых красных связей</span> |
'''Node''' fixUp(h : '''Node''') | '''Node''' fixUp(h : '''Node''') | ||
'''if''' (isRed(h.right)) | '''if''' (isRed(h.right)) | ||
h = rotateLeft(h) | h = rotateLeft(h) | ||
− | <span style="color:#008000">//Вращение <tex>2</tex>-ой красной пары пары</span> | + | <span style="color:#008000">// Вращение <tex>2</tex>-ой красной пары пары</span> |
'''if''' (isRed(h.left) '''&&''' isRed(h.left.left)) | '''if''' (isRed(h.left) '''&&''' isRed(h.left.left)) | ||
h = rotateRight(h) | h = rotateRight(h) | ||
− | <span style="color:#008000">//Балансировка узла с <tex>4</tex>-я потомками</span> | + | <span style="color:#008000">// Балансировка узла с <tex>4</tex>-я потомками</span> |
'''if''' (isRed(h.left) '''&&''' isRed(h.right)) | '''if''' (isRed(h.left) '''&&''' isRed(h.right)) | ||
colorFlip(h) | colorFlip(h) | ||
Строка 163: | Строка 163: | ||
'''Node''' deleteMax(h : '''Node''') | '''Node''' deleteMax(h : '''Node''') | ||
if (isRed(h.left)) | if (isRed(h.left)) | ||
− | <span style="color:#008000">//вращаем все 3-вершины вправо</span> | + | <span style="color:#008000">// вращаем все 3-вершины вправо</span> |
h = rotateRight(h) | h = rotateRight(h) | ||
− | <span style="color:#008000">//поддерживаем инвариант (h должен быть красным)</span> | + | <span style="color:#008000">// поддерживаем инвариант (h должен быть красным)</span> |
if (h.right == null) | if (h.right == null) | ||
return null | return null | ||
− | <span style="color:#008000">//заимствуем у брата если необходимо</span> | + | <span style="color:#008000">// заимствуем у брата если необходимо</span> |
if (!isRed(h.right) '''&&''' !isRed(h.right.left)) | if (!isRed(h.right) '''&&''' !isRed(h.right.left)) | ||
h = moveRedRight(h) | h = moveRedRight(h) | ||
<span style="color:#008000">// опускаемся на один уровень глубже </span> | <span style="color:#008000">// опускаемся на один уровень глубже </span> | ||
h.left = deleteMax(h.left) | h.left = deleteMax(h.left) | ||
− | <span style="color:#008000">//исправление правых красных ссылок и 4-вершин на пути вверх</span> | + | <span style="color:#008000">// исправление правых красных ссылок и 4-вершин на пути вверх</span> |
'''return''' fixUp(h) | '''return''' fixUp(h) | ||
Строка 196: | Строка 196: | ||
'''Node''' deleteMin(h : '''Node''') | '''Node''' deleteMin(h : '''Node''') | ||
− | <span style="color:#008000">//удаляем узел на нижнем уровне(h должен быть красным по инварианту)</span> | + | <span style="color:#008000">// удаляем узел на нижнем уровне(h должен быть красным по инварианту)</span> |
if (h.left == ''null'') | if (h.left == ''null'') | ||
'''return''' ''null'' | '''return''' ''null'' | ||
− | <span style="color:#008000">//Если необходимо, пропушим красную ссылку вниз</span> | + | <span style="color:#008000">// Если необходимо, пропушим красную ссылку вниз</span> |
'''if (!'''isRed(h.left) '''&& !'''isRed(h.left.left)) | '''if (!'''isRed(h.left) '''&& !'''isRed(h.left.left)) | ||
h = moveRedLeft(h) | h = moveRedLeft(h) | ||
− | <span style="color:#008000">//опускаемся на уровень ниже </span> | + | <span style="color:#008000">// опускаемся на уровень ниже </span> |
h.left = deleteMin(h.left) | h.left = deleteMin(h.left) | ||
'''return''' fixUp(h) | '''return''' fixUp(h) |
Версия 14:46, 18 июня 2018
Определение: |
Левосторонние красно-черные деревья — двоичные деревья поиска, в котором баланс осуществляется на основе "цвета" узла дерева, который принимает только два значения: "красный" и "чёрный". Данный тип красно-черных деревьев имеет ряд преимуществ над классической структурой. Разработан Робертом Соджевиском в году. |
Содержание
Вращения
Чтобы поддерживать левосторонние красно-черные двоичные деревья поиска необходимо соблюдать следующие инварианты при вставке и удалении:
- Ни один путь от корня до листьев дерева не содержит двух последовательных красных узлов.
- Количество черных узлов на каждом таком пути одинаково.
Из этих инвариантов следует, что длина каждого пути от корня до листьев в красно-черном дереве с
узлами не превышает .Основные операции, используемые алгоритмами сбалансированного дерева для поддержания баланса при вставке и удалении, называются вращениями. Эти операции трансформируют
-узел, левый потомок которого окрашен в красный, в -узел, правый потомок которого окрашен в красный и наоборот. Вращения сохраняют два указанных выше инварианта, не изменяют поддеревья узла.Псевокод
Node rotateRight(h : Node) x = h.left h.left= x.right x.right= h x.color = h.color h.color = RED return x
Node rotateLeft(h : Node) x = h.right h.right = x.left x.left = h x.color = h.color h.color = RED return x
Переворот цветов
В красно-черных деревьях используется такая операция как Переворот цветов , которая инвертирует цвет узла и двух его детей. Она не изменяет количество черных узлов при любом обходе от корня до листьев дерева, но может привести к появлению двух последовательных красных узлов.
void flipColors(h : Node h)
h.color = ! h.color
h.left.color = ! h.left.color
h.right.color =
h.right.color
Вставка
Вставка в ЛСКЧД базируется на
простых операциях:- Вставка нового узла к листу дерева:
Если высота узла нулевая, возвращаем новый красный узел.
if (h == null) return new Node(key, value, RED)
- Расщепление узла с -я потомками:
Если левый предок и правый предок красные, запускаем вращение цветов от текущего узла.
if (isRed(h.left) && isRed(h.right)) colorFlip(h)
- Принудительное вращение влево:
Если правый предок красный, вращаем текущую вершину влево.
if (isRed(h.right)) h = rotateLeft(h)
- Балансировка узла с -я потомками:
Если левый предок красный и левый предок левого предка красный, то вращаем текущую вершину вправо.
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h)
Псевдокод
void insert(key : Key, value : Value ) root = insert(root, key, value) root.color = BLACK
Node insert(h : Node, key : Key, value : Value) // Вставка нового узла к листу дерева if (h == null) return new Node(key, value) // Расщепление узла с if (isRed(h.left) && isRed(h.right)) colorFlip(h) -я потомками// Стандартная вставка в дереве поиска int cmp = key.compareTo(h.key) if (cmp == 0) h.val = value else if cmp < 0 h.left = insert(h.left, key, value) else h.right = insert(h.right, key, value) // Принудительное вращение влево if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h) // Балансировка узла с if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h) return h -я потомками
Поиск
Поиск в левосторонних красно-черных деревьях эквивалентен поиску в наивной реализации дерева поиска. Для поиска элемента в красно-черных деревьях дереве поиска можно воспользоваться циклом,который проходит от вершины до искомого элемента. Если же элемент отсутствует, цикл пройдет до листа дерева и прервется. Для каждого узла цикл сравнивает значение его ключа с искомым ключом. Если ключи одинаковы, то функция возвращает текущий узел, в противном случае цикл повторяет для левого или правого поддерева. Узлы, которые посещает функция образуют нисходящий путь от корня, так что время ее работы , где — высота дерева.
Псевдокод
Value search(key : Key) Node x = root while (x != null) int cmp = key.compareTo(x.key) if (cmp == 0) return x.val else if (cmp < 0) x = x.left else if (cmp > 0) x = x.right return null
Удаление
Исправление правых красных связей
- Использование Переворота цветов и вращений сохраняет баланс черной связи.
- После удаления необходимо исправить правые красные связи и устранить узлы с -я потомками
// Исправление правых красных связей Node fixUp(h : Node) if (isRed(h.right)) h = rotateLeft(h) // Вращение if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h) -ой красной пары пары// Балансировка узла с if (isRed(h.left) && isRed(h.right)) colorFlip(h) return h -я потомками
Удаление максимума
- Спускаемся вниз по правому краю дерева.
- Если поиск заканчивается на узле с -мя или -ю потомками, просто удаляем узел.
- Удаление узла с -я потомками нарушает баланс
Соответственно, спускаясь вниз по дереву необходимо поддерживать следующий инвариант : количество потомков узла не должно быть ровно
-м.Будем поддерживать инвариант: для любого узла либо сам узел, либо правый предок узла красный. Будем придерживаться тактики , что удалять лист легче, чем внутренний узел.
Заметим, что если правый потомок вершины и правый потомок правого потомка вершины черные, необходимо переместить левую красную ссылку вправо для сохранения инварианта.
Псевдокод
void deleteMax() root = deleteMax(root) root.color = BLACK
Node moveRedLeft(h : Node) colorFlip(h) if (isRed(h.right.left) h.right = rotateRight(h.right) h = rotateLeft(h) colorFlip(h) return h
Node deleteMax(h : Node) if (isRed(h.left)) // вращаем все 3-вершины вправо h = rotateRight(h) // поддерживаем инвариант (h должен быть красным) if (h.right == null) return null // заимствуем у брата если необходимо if (!isRed(h.right) && !isRed(h.right.left)) h = moveRedRight(h) // опускаемся на один уровень глубже h.left = deleteMax(h.left) // исправление правых красных ссылок и 4-вершин на пути вверх return fixUp(h)
Удаление минимума
Поддерживаем инвариант: вершина или левый ребенок вершины красный.
Заметим, что если левый потомок вершины и левый потомок левого потомка вершины черные, необходимо переместить красную ссылку для сохранения инварианта.
Псевдокод
Node moveRedLeft(h : Node) colorFlip(h) if (isRed(h.right.left)) h.right = rotateRight(h.right) h = rotateLeft(h) colorFlip(h) return h
void deleteMin() root = deleteMin(root) root.color = BLACK
Node deleteMin(h : Node) // удаляем узел на нижнем уровне(h должен быть красным по инварианту) if (h.left == null) return null // Если необходимо, пропушим красную ссылку вниз if (!isRed(h.left) && !isRed(h.left.left)) h = moveRedLeft(h) // опускаемся на уровень ниже h.left = deleteMin(h.left) return fixUp(h)
Асимптотика
Асимптотика методов в левосторонних красно-черных деревьях эквивалентна асимптотике левосторонних красно-черных деревьях.
См.также
Источники информации
- Robert Sedgewick "Left-leaning Red-Black Trees" ,Department of Computer Science, Princeton University