Левосторонние красно-чёрные деревья — различия между версиями
м (rollbackEdits.php mass rollback) |
|||
| (не показано 86 промежуточных версий 4 участников) | |||
| Строка 1: | Строка 1: | ||
{{Определение | {{Определение | ||
| − | |definition = | + | |definition = Левостороннее красно-черное дерево {{---}} тип сбалансированного двоичного дерева поиска, гарантирующий такую же асимптотическую сложность операций, как у красно-черного дерева поиска. |
}} | }} | ||
| − | == | + | |
| − | * | + | ==Свойства== |
| − | * | + | *Корневой узел всегда черный. |
| − | * | + | *Каждый новый узел всегда окрашен в красный цвет. |
| + | *Каждый дочерний нулевой узел листа дерева считается черным. | ||
==Вращения== | ==Вращения== | ||
| − | Чтобы поддерживать красно-черные | + | Чтобы поддерживать левосторонние красно-черные двоичные деревья поиска необходимо соблюдать следующие инварианты при вставке и удалении: |
| − | * Ни один | + | * Ни один путь от корня до листьев дерева не содержит двух последовательных красных узлов. |
* Количество черных узлов на каждом таком пути одинаково. | * Количество черных узлов на каждом таком пути одинаково. | ||
| − | |||
| − | Основные операции, используемые алгоритмами сбалансированного дерева для поддержания баланса при вставке и удалении, называются | + | Основные операции, используемые алгоритмами сбалансированного дерева для поддержания баланса при вставке и удалении, называются вращением вправо и вращением влево. Первая операция трансформируют <tex>3</tex>-узел (совокупность из <tex>3</tex> узлов, где <tex>2</tex> узла являются наследниками третьего, причем одна из связей является красной), левый потомок которого окрашен в красный, в <tex>3</tex>-узел, правый потомок которого окрашен в красный,вторая операция {{---}} наоборот. Вращения сохраняют два указанных выше инварианта, не изменяют поддеревья узла. |
| − | === | + | ===Псевдокод=== |
[[File:rotateRight.png|310px|thumb|upright|Rotate Right]] | [[File:rotateRight.png|310px|thumb|upright|Rotate Right]] | ||
| − | '''Node''' rotateRight( h : '''Node''') | + | '''Node''' rotateRight(h : '''Node''') |
x = h.left | x = h.left | ||
| − | h.left= x.right | + | h.left = x.right |
| − | x.right= h | + | x.right = h |
x.color = h.color | x.color = h.color | ||
h.color = RED | h.color = RED | ||
| − | + | '''return''' x | |
[[File:rotateLeft.png|310px|thumb|upright|Rotate Left]] | [[File:rotateLeft.png|310px|thumb|upright|Rotate Left]] | ||
| − | '''Node''' rotateLeft( h : '''Node''') | + | '''Node''' rotateLeft(h : '''Node''') |
x = h.right | x = h.right | ||
h.right = x.left | h.right = x.left | ||
| Строка 30: | Строка 30: | ||
x.color = h.color | x.color = h.color | ||
h.color = RED | h.color = RED | ||
| − | + | '''return''' x | |
==Переворот цветов== | ==Переворот цветов== | ||
| − | + | В красно-черных деревьях используется такая операция как '''переворот цветов''' , которая инвертирует цвет узла и двух его детей. Она не изменяет количество черных узлов на любом пути от корня до листа, но может привести к появлению двух последовательных красных узлов. | |
| − | В красно-черных деревьях используется такая операция как ''' | + | ===Псевдокод=== |
| − | [[File: ColorFlip.png|320px|thumb|upright| | + | [[File: ColorFlip.png|320px|thumb|upright| Переворот цветов]] |
| − | + | '''void''' flipColors(h : '''Node''' h) | |
| − | '''void''' flipColors( h : '''Node''' h) | ||
h.color = '''!''' h.color | h.color = '''!''' h.color | ||
| − | h.left.color = | + | h.left.color = '''!''' h.left.color |
| − | h.right.color = | + | h.right.color = '''!''' h.right.color |
==Вставка== | ==Вставка== | ||
| Строка 48: | Строка 47: | ||
Если высота узла нулевая, возвращаем новый красный узел. | Если высота узла нулевая, возвращаем новый красный узел. | ||
[[File:insertNode.png|310px|thumb|upright|Вставка нового узла]] | [[File:insertNode.png|310px|thumb|upright|Вставка нового узла]] | ||
| − | |||
| − | |||
| − | |||
| − | |||
*Расщепление узла с <tex>4</tex>-я потомками: | *Расщепление узла с <tex>4</tex>-я потомками: | ||
| − | Если левый предок и правый предок красные, запускаем | + | Если левый предок и правый предок красные, запускаем переворот цветов от текущего узла. |
[[File:Split4node.png|310px|thumb|upright|Расщепление узла]] | [[File:Split4node.png|310px|thumb|upright|Расщепление узла]] | ||
| − | |||
| − | |||
| − | |||
*Принудительное вращение влево: | *Принудительное вращение влево: | ||
[[File:Enforce.png|310px|thumb|upright|Принудительное вращение]] | [[File:Enforce.png|310px|thumb|upright|Принудительное вращение]] | ||
Если правый предок красный, вращаем текущую вершину влево. | Если правый предок красный, вращаем текущую вершину влево. | ||
| − | |||
| − | |||
| − | |||
*Балансировка узла с <tex>4</tex>-я потомками: | *Балансировка узла с <tex>4</tex>-я потомками: | ||
[[File:Balance4node.png|310px|thumb|Балансировка]] | [[File:Balance4node.png|310px|thumb|Балансировка]] | ||
Если левый предок красный и левый предок левого предка красный, то вращаем текущую вершину вправо. | Если левый предок красный и левый предок левого предка красный, то вращаем текущую вершину вправо. | ||
| − | + | ||
| − | |||
===Псевдокод=== | ===Псевдокод=== | ||
| − | '''void''' insert( ''' | + | '''void''' insert(key : '''Key''', value : '''Value''' ) |
root = insert(root, key, value) | root = insert(root, key, value) | ||
root.color = BLACK | root.color = BLACK | ||
| − | '''Node''' insert( h : '''Node''', key : '''Key''', value : '''Value''') | + | '''Node''' insert(h : '''Node''', key : '''Key''', value : '''Value''') |
| − | <span style="color:#008000">//Вставка нового | + | <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> |
| − | ''' | + | '''if''' key = h.key |
| − | |||
h.val = value | h.val = value | ||
'''else''' | '''else''' | ||
| − | '''if''' | + | '''if''' key < h.key |
h.left = insert(h.left, key, value) | h.left = insert(h.left, key, value) | ||
'''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) | ||
| − | + | '''return''' h | |
==Поиск== | ==Поиск== | ||
Поиск в левосторонних красно-черных деревьях эквивалентен поиску в [[Дерево поиска, наивная реализация|наивной реализации дерева поиска]]. | Поиск в левосторонних красно-черных деревьях эквивалентен поиску в [[Дерево поиска, наивная реализация|наивной реализации дерева поиска]]. | ||
| − | Для поиска элемента в красно-черных деревьях дереве поиска можно воспользоваться циклом, проходит от | + | Для поиска элемента в красно-черных деревьях дереве поиска можно воспользоваться циклом,который проходит от корня до искомого элемента. Если же элемент отсутствует, цикл пройдет до листа дерева и прервется. Для каждого узла цикл сравнивает значение его ключа с искомым ключом. Если ключи одинаковы, то функция возвращает текущий узел, в противном случае цикл повторяет для левого или правого поддерева. Узлы, которые посещает функция образуют нисходящий путь от корня, так что время ее работы <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева. |
===Псевдокод=== | ===Псевдокод=== | ||
| − | '''Value''' search(key : '''Key''') | + | '''Value''' search(key : '''Key''') |
'''Node''' x = root | '''Node''' x = root | ||
'''while''' (x '''!'''= null) | '''while''' (x '''!'''= null) | ||
| − | ''' | + | '''if''' key = x.key |
| − | |||
'''return''' x.val | '''return''' x.val | ||
'''else''' | '''else''' | ||
| − | '''if''' | + | '''if''' key < x.key |
x = x.left | x = x.left | ||
'''else''' | '''else''' | ||
| − | '''if''' | + | '''if''' key > x.key |
x = x.right | x = x.right | ||
| − | + | '''return''' ''null'' | |
| − | + | ==Исправление правых красных связей== | |
| − | |||
*Использование Переворота цветов и вращений сохраняет баланс черной связи. | *Использование Переворота цветов и вращений сохраняет баланс черной связи. | ||
| − | *После удаления необходимо исправить правые красные связи и устранить узлы с <tex>4 | + | *После удаления необходимо исправить правые красные связи и устранить узлы с <tex>4</tex>-я потомками |
| − | <span style="color:#008000">//Исправление правых красных связей</span> | + | <span style="color:#008000">// Исправление правых красных связей</span> |
| − | '''Node''' fixUp(h : '''Node''') | + | '''Node''' fixUp(h : '''Node''') |
| − | '''if''' | + | '''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''' | + | '''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''' | + | '''if''' isRed(h.left) '''&&''' isRed(h.right) |
| − | colorFlip(h) | + | colorFlip(h) |
| − | + | '''return''' h | |
| − | + | ==Удаление максимума== | |
* Спускаемся вниз по правому краю дерева. | * Спускаемся вниз по правому краю дерева. | ||
* Если поиск заканчивается на узле с <tex>4</tex>-мя или <tex>5</tex>-ю потомками, просто удаляем узел. | * Если поиск заканчивается на узле с <tex>4</tex>-мя или <tex>5</tex>-ю потомками, просто удаляем узел. | ||
[[File:34-nodeRemove.png|310px|thumb|center| Узлы до и после удаления]] | [[File:34-nodeRemove.png|310px|thumb|center| Узлы до и после удаления]] | ||
| − | * Удаление узла с <tex>2</tex>-я потомками | + | * Удаление узла с <tex>2</tex>-я потомками нарушает баланс |
| − | Соответственно спускаясь вниз по дереву необходимо поддерживать следующий инвариант : количество потомков узла не должно быть ровно <tex>2</tex>-м. | + | Соответственно, спускаясь вниз по дереву необходимо поддерживать следующий инвариант : количество потомков узла не должно быть ровно <tex>2</tex>-м. |
[[File:changeNode.png|600px|thumb|center| ]] | [[File:changeNode.png|600px|thumb|center| ]] | ||
| − | Будем поддерживать инвариант : | + | Будем поддерживать инвариант: для любого узла либо сам узел, либо правый предок узла '''красный'''. |
Будем придерживаться тактики , что удалять лист легче, чем внутренний узел. | Будем придерживаться тактики , что удалять лист легче, чем внутренний узел. | ||
| Строка 152: | Строка 137: | ||
===Псевдокод=== | ===Псевдокод=== | ||
| − | '''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 | + | '''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 | |
| − | '''Node''' deleteMax(h : '''Node''') | + | '''Node''' deleteMax(h : '''Node''') |
| − | if | + | '''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 | + | '''if''' h.right == ''null'' |
| − | return null | + | return ''null'' |
| − | <span style="color:#008000">//заимствуем у брата если необходимо</span> | + | <span style="color:#008000">// заимствуем у брата если необходимо</span> |
| − | if | + | '''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) |
==Удаление минимума== | ==Удаление минимума== | ||
| Строка 187: | Строка 172: | ||
===Псевдокод=== | ===Псевдокод=== | ||
'''Node''' moveRedLeft(h : '''Node''') | '''Node''' moveRedLeft(h : '''Node''') | ||
| − | colorFlip(h) | + | colorFlip(h) |
| − | if | + | 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 | |
'''void''' deleteMin() | '''void''' deleteMin() | ||
| − | root = deleteMin(root) | + | root = deleteMin(root) |
| − | root.color = BLACK | + | root.color = BLACK |
'''Node''' deleteMin(h : '''Node''') | '''Node''' deleteMin(h : '''Node''') | ||
| − | <span style="color:#008000">//удаляем узел на нижнем уровне(h должен быть красным по инварианту)</span> | + | <span style="color:#008000">// удаляем узел на нижнем уровне(h должен быть красным по инварианту)</span> |
| − | if | + | 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) | |
==Асимптотика== | ==Асимптотика== | ||
| − | Асимптотика методов в левосторонних красно-черных деревьях эквивалентна асимптотике [[Красно-черное дерево| | + | Асимптотика методов в левосторонних красно-черных деревьях эквивалентна асимптотике [[Красно-черное дерево|базовой реализации красно-черных деревьях]]. |
| − | |||
== См.также == | == См.также == | ||
Текущая версия на 19:08, 4 сентября 2022
| Определение: |
| Левостороннее красно-черное дерево — тип сбалансированного двоичного дерева поиска, гарантирующий такую же асимптотическую сложность операций, как у красно-черного дерева поиска. |
Свойства
- Корневой узел всегда черный.
- Каждый новый узел всегда окрашен в красный цвет.
- Каждый дочерний нулевой узел листа дерева считается черным.
Вращения
Чтобы поддерживать левосторонние красно-черные двоичные деревья поиска необходимо соблюдать следующие инварианты при вставке и удалении:
- Ни один путь от корня до листьев дерева не содержит двух последовательных красных узлов.
- Количество черных узлов на каждом таком пути одинаково.
Основные операции, используемые алгоритмами сбалансированного дерева для поддержания баланса при вставке и удалении, называются вращением вправо и вращением влево. Первая операция трансформируют -узел (совокупность из узлов, где узла являются наследниками третьего, причем одна из связей является красной), левый потомок которого окрашен в красный, в -узел, правый потомок которого окрашен в красный,вторая операция — наоборот. Вращения сохраняют два указанных выше инварианта, не изменяют поддеревья узла.
Псевдокод
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
Вставка
Вставка в ЛСКЧД базируется на простых операциях:
- Вставка нового узла к листу дерева:
Если высота узла нулевая, возвращаем новый красный узел.
- Расщепление узла с -я потомками:
Если левый предок и правый предок красные, запускаем переворот цветов от текущего узла.
- Принудительное вращение влево:
Если правый предок красный, вращаем текущую вершину влево.
- Балансировка узла с -я потомками:
Если левый предок красный и левый предок левого предка красный, то вращаем текущую вершину вправо.
Псевдокод
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)
// Стандартная вставка в дереве поиска
if key = h.key
h.val = value
else
if key < h.key
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)
if key = x.key
return x.val
else
if key < x.key
x = x.left
else
if key > x.key
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