Левосторонние красно-чёрные деревья — различия между версиями
(→Исправление правых красных связей) |
(→Псевдокод) |
||
Строка 148: | Строка 148: | ||
===Псевдокод=== | ===Псевдокод=== | ||
− | '''void''' deleteMax() | + | '''void''' deleteMax() |
− | root = deleteMax(root); | + | root = deleteMax(root); |
− | root.color = BLACK; | + | root.color = BLACK; |
'''Node''' moveRedLeft(h : '''Node''') | '''Node''' moveRedLeft(h : '''Node''') | ||
− | colorFlip(h); | + | colorFlip(h); |
− | if (isRed(h.right.left) | + | if (isRed(h.right.left) |
− | h.right = rotateRight(h.right); | + | h.right = rotateRight(h.right); |
− | h = rotateLeft(h); | + | h = rotateLeft(h); |
− | colorFlip(h); | + | colorFlip(h); |
− | '''return''' h; | + | '''return''' h; |
'''Node''' deleteMax(h : '''Node''') | '''Node''' deleteMax(h : '''Node''') | ||
− | if (isRed(h.left)) | + | if (isRed(h.left)) |
− | //вращаем все 3-вершины вправо | + | <span style="color:#008000">//вращаем все 3-вершины вправо</span> |
h = rotateRight(h); | h = rotateRight(h); | ||
− | //поддерживаем инвариант (h должен быть красным) | + | <span style="color:#008000">//поддерживаем инвариант (h должен быть красным)</span> |
if (h.right == null) | if (h.right == null) | ||
return null; | return null; | ||
− | //заимствуем у брата если необходимо | + | <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> |
h.left = deleteMax(h.left); | h.left = deleteMax(h.left); | ||
− | //исправление правых красных ссылок и 4-вершин на пути вверх | + | <span style="color:#008000">//исправление правых красных ссылок и 4-вершин на пути вверх</span> |
'''return''' fixUp(h); | '''return''' fixUp(h); | ||
Версия 18:22, 14 мая 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) //Расщепление узла с в дереве поиска 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-я потомками if isRed(h.left) && isRed(h.right) colorFlip(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