
- •Введение
- •1. Структуры данных: деревья
- •1.1 Структуры данных
- •1.2 Деревья
- •1.3 Бинарные деревья
- •1.4 Бинарные деревья поиска
- •1.5 Красно-черные деревья
- •1.6 Операции над красно-черными деревьями
- •1.6.1 Повороты
- •1.6.2 Вставка
- •1.6.3 Удаление
- •1.6.4 Поиск узла с заданным ключом
- •1.6.5 Поиск минимума и максимума
- •2. Автоматизация операций над красно-чёрными деревьями
- •Заключение
- •Список литературы:
- •Приложение 1. Листинг программы
1.6 Операции над красно-черными деревьями
1.6.1 Повороты
Операции над деревом поиска Tree_Insert и Tree_Delete, будучи применены к красно-черному дереву с n ключами, выполняются за время O(lg(n)). Поскольку они изменяют дерево, в результате их работы могут нарушаться красно-черные свойства. Для восстановления этих свойств мы должны изменить цвета некоторых узлов дерева, а также структуру его указателей.
Рис. 8. Операции поворота в бинарном дереве поиска.
Изменения в
структуре указателей будут выполняться
при помощи поворотов
(rotations), которые представляют собой
локальные операции в дереве поиска,
сохраняющие свойство бинарного дерева
поиска. На рис. 6 показаны два типа
поворотов - левый и правый (здесь
,
и
- произвольные поддеревья). При выполнении
левого поворота в узле x
предполагается, что его правый дочерний
узел y
не является листом nil[T].
Левый поворот выполняется "вокруг"
связи между x
и y,
делая y
новым корнем поддерева, левым дочерним
узлом которого становится x,
а бывший левый потомок узла y
- правым потомком x.
В псевдокоде
процедуры LEFT_ROTATE
предполагается, что
,
а родитель корневого узла - nil[Т].
Left_Rotate(T, x)
1
>
Устанавливаем у.
2
>
Левое поддерево у
становится
> правым поддеревом х
3 if
4 then
5
>
Перенос родителя х
в у
6 if
7 then
8 else
if
9
then
10 else
11
>
x
- левый дочерний y
12
Рис. 9. Пример выполнения процедуры LEFT_ROTATE.
Код процедуры Right_Rotate симметричен коду Left_Rotate. Обе эти процедуры выполняются за время O(1). При повороте изменяются только указатели, все остальные поля сохраняют свое значение [3].
1.6.2 Вставка
Вставка узла в красно-черное дерево с n узлами может быть выполнена за время О(lg(n)). Для того чтобы вставка сохраняла красно-черные свойства дерева, после нее вызывается вспомогательная процедура RB_INSERT_FIXUP, которая перекрашивает узлы и выполняет повороты. Вызов RB_Insert(T, z) вставляет в красно-черное дерево Т узел z с заполненным полем key:
RB Insert(T, z)
1
2
3 while
4 do
5
if
6
then
7
else
8
9 if
10 then
11 else
if
12
then
13
else
14
15
16
17 RB_INSERT_FIXUP(T, z)
Поскольку красный цвет z может вызвать нарушение одного из красно-черных свойств, в строке 17 вызывается вспомогательная процедура RB_Insert_Fixup(T, z), предназначение которой - восстановить красно-черные свойства дерева:
RB_Insert_Fixup(T, z)
1 while
2 do
if
3 then
4 if
5 then
> Случай 1
6
> Случай 1
7
> Случай 1
8
> Случай 1
9 else
if
10 then z 4- p[z] > Случай 2
11 Left_Rotate(T, z) > Случай 2
12 > Случай 3
13 > Случай 3
14
> Случай 3
15 else (то же, что и в "then", с заменой left на right и наоборот)
16
Для того чтобы понять, как работает процедура RB_INSERT_FIXUP, разобьем рассмотрение кода на три основные части. Сначала определим, какие из красно-черных свойств нарушаются при вставке узла z и окраске его в красный цвет. Затем рассмотрим предназначение цикла while в строках 1-15. После этого изучим каждый из трех случаев, которые встречаются в этом цикле, и посмотрим, каким образом достигается цель в каждом случае. На рис. 8 показан пример выполнения процедуры RB_INSERT_FIXUP.
Какие из красно-черных свойств могут быть нарушены перед вызовом RB_InSERT_Fixup? Свойство 1 определенно выполняется (как и свойство 3), так как оба дочерних узла вставляемого узла являются ограничителями nil[T]. Свойство 5, согласно которому для каждого узла все пути от него до листьев, являющихся потомками данного узла, содержат одно и то же количество черных узлов, также остается в силе, поскольку узел z замещает (черный) ограничитель, будучи при этом красным и имея черные дочерние узлы. Таким образом, может нарушаться только свойство 2, которое требует, чтобы корень красно-черного дерева был черным, и свойство 4, согласно которому красный узел не может иметь красного потомка. Оба нарушения возможны в силу того, что узел z после вставки окрашивается в красный цвет. Свойство 2 оказывается нарушенным, если узел z становится корнем, а свойство 4 - если родительский по отношению к z узел является красным. На рис. 8а показано нарушение свойства 4 после вставки узла z.
Цикл while в строках 1-15 сохраняет следующий инвариант, состоящий из трех частей.
В начале каждой итерации цикла:
а) узел z красный;
б) если р[z] - корень дерева, то р[z] — черный узел;
в) если имеется нарушение красно-черных свойств, то это нарушение только одно - либо нарушение свойства 2, либо свойства 4. Если нарушено свойство 2, то это вызвано тем, что корнем дерева является красный узел z; если нарушено свойство 4, то в этом случае красными являются узлы z и p[z].
Часть в, в которой говорится о возможных нарушениях красно-черных свойств, наиболее важна для того, чтобы показать, что процедура RB_Insert_Fixup восстанавливает красно-черные свойства. Части а и б просто поясняют ситуацию.
Поскольку мы
сосредотачиваем свое рассмотрение
только на узле z
и узлах, находящихся в дереве вблизи
него, полезно знать, что узел z
- красный (часть а). Часть
б используется
для того, чтобы показать, что узел
к
которому мы обращаемся в строках 2, 3, 7,
8, 13 и 14, существует.
Инициализация. Перед выполнением первой итерации цикла имеется красно-черное дерево без каких-либо нарушений красно-черных свойств, к которому мы добавляем красный узел z. Покажем, что все части инварианта цикла выполняются к моменту вызова процедуры RB_Insert_Fixup.
а) В момент вызова процедуры RB_Insert_Fixup узел z — вставленный в дерево красный узел.
б) Если р[z] - корневой узел дерева, то он является черным и не изменяется до вызова процедуры RB_lNSERT_Fixup.
в) Мы уже убедились в том, что красно-черные свойства 1, 3 и 5 сохраняются к моменту вызова процедуры RB_Insert_Fixup.
Если нарушается свойство 2, то красный корень должен быть добавленным в дерево узлом z, который при этом является единственным внутренним узлом дерева. Поскольку и родитель, и оба потомка z являются ограничителями, свойство 4 не нарушается. Таким образом, нарушение свойства 2 - единственное нарушение красно-черных свойств во всем дереве.
Если же нарушено свойство 4, то поскольку дочерние по отношению к z узлы являются черными ограничителями, а до вставки z никаких нарушений красно-черных свойств в дереве не было, нарушение заключается в том, что и z, и р[z] - красные. Кроме этого, других нарушений красно-черных свойств не имеется.
Завершение. Как видно из кода, цикл завершает свою работу, когда р[z] становится черным (если z - корневой узел, то р[z] представляет собой черный ограничитель nil[Т]). Таким образом, свойство 4 при завершении цикла не нарушается. В соответствии с инвариантом цикла, единственным нарушением красно-черных свойств может быть нарушение свойства 2. В строке 16 это свойство восстанавливается, так что по завершении работы процедуры RB_Insert_Fixup все красно-черные свойства дерева выполняются.
Сохранение. При работе цикла while следует рассмотреть шесть разных случаев, однако три из них симметричны другим трем; разница лишь в том, является ли родитель р[z] левым или правым дочерним узлом по отношению к своему родителю р[р[z]], что и выясняется в строке 2 (мы привели код только для ситуации, когда р[z] является левым потомком). Узел р[р[z]] существует, поскольку, в соответствии с частью б) инварианта цикла, если р[z] - корень дерева, то он черный. Поскольку цикл начинает работу, только если р[z] - красный, то р[z] не может быть корнем. Следовательно, р[р[z]] существует. Случай 1 отличается от случаев 2 и 3 цветом "брата" родительского по отношению к z узла, т.е. "дяди" узла z. После выполнения строки 3 указатель у указывает на дядю узла z - узел right[р[р[z]]], и в строке 4 выясняется его цвет. Если у - красный, выполняется код для случая 1; в противном случае выполняется код для случаев 2 и 3. В любом случае, узел р[р[z]] - черный, поскольку узел р[z] - красный, а свойство 4 нарушается только между z np[z].
Рис. 10. Работа процедуры RB_INSERT_FIXUP
Случай 1: узел у красный.
На рис. 11 показана ситуация, возникающая в случае 1 (строки 5-8), когда и p[z], и у - красные узлы. Поскольку p[p[z]] - черный, мы можем исправить ситуацию, покрасив и p[z], и у в черный цвет (после чего цвет красного родителя узла z становится черным, и нарушение между z и его родителем исчезает), а р[р[z]] - в красный цвет, для того чтобы выполнялось свойство 5. После этого мы повторяем цикл while с узлом р[р[z]] в качестве нового узла z. Указатель z перемещается, таким образом, на два уровня вверх.
На рис. 11а z
- правый дочерний узел, а на рис. 11б -
левый. Как видите, предпринимаемые в
обоих случаях одни и те же действия
приводят к одинаковому результату. Все
поддеревья (
и
)
имеют черные корни и одинаковое значение
черной высоты. После перекраски свойство
5 сохраняется: все нисходящие пути от
узла к листьям содержат одинаковое
число черных узлов. После выполнения
итерации новым узлом z
становится узел р[р[z]]
и нарушение свойства 4 может быть только
между новым узлом z
и его родителем (который также может
оказаться красным).
Теперь покажем, что в случае 1 инвариант цикла сохраняется. Обозначим через z узел z в текущей итерации, а через z'=р[р[z]] - узел z, проверяемый в строке 1 при следующей итерации.
а) Поскольку в данной итерации цвет узла р[р[z]] становится красным, в начале следующей итерации узел z' - красный.
б) Узел р[z'] в текущей итерации - р[р[р[z]]], и цвет данного узла в пределах данной итерации не изменяется. Если это корневой узел, то его цвет до начала данной итерации был черным и остается таковым в начале следующей итерации.
в) Мы уже доказали, что в случае 1 свойство 5 сохраняется; кроме того, понятно, что при выполнении итерации не возникает нарушение свойств 1 или 3.
Рис. 11. Случай 1 процедуры RB_lNSERT_FlXUP
Если узел z' в начале очередной итерации является корнем, то код, соответствующий случаю 1, корректирует единственное нарушение свойства 4. Поскольку узел z' - красный и корневой, единственным нарушенным становится свойство 2, причем это нарушение связано с узлом z'.
Если узел z' в начале следующей итерации корнем не является, то код, соответствующий случаю 1, не вызывает нарушения свойства 2. Этот код корректирует единственное нарушение свойства 4, имеющееся перед выполнением итерации. Коррекция выражается в том, что узел z' становится красным, в то время как цвет узла р[z'] не изменяется. Если узел р[z'] был черным, то свойство 4 не нарушается; если же этот узел был красным, то окрашивание узла z' в красный цвет приводит к нарушению свойства 4 между узлами z' и р[z'].
Случай 2: узел у черный и z — правый потомок.
Случай 3: узел у черный и z — левый потомок.
В случаях 2 и 3 цвет узла у, являющегося "дядей" узла z, черный. Эти два случая отличаются друг от друга тем, что z является левым или правым дочерним узлом по отношению к родительскому. Строки 10-11 псевдокода соответствуют случаю 2, который показан на рис. 12 вместе со случаем 3. В случае 2 узел z является правым потомком своего родительского узла. Используем левый поворот для преобразования сложившейся ситуации в случай 3 (строки 12-14), когда z является левым потомком. Поскольку и z, и р[z] - красные узлы, поворот не влияет ни на черную высоту узлов, ни на выполнение свойства 5. Когда мы приходим к случаю 3 (либо непосредственно, либо поворотом из случая 2), узел у имеет черный цвет (поскольку иначе мы бы получили случай 1). Кроме того, обязательно существует узел р[р[z]], так как мы доказали, что этот узел существовал при выполнение строк 2 и 3, а также что после перемещения узла z на один узел вверх в строке 10 с последующим опусканием в строке 11 узел р[р[z]] остается неизменным. В случае 3 мы выполняем ряд изменений цвета и правых поворотов, которые сохраняют свойство 5. После этого, так как у нас нет двух идущих подряд красных узлов, работа процедуры завершается. Больше тело цикла while не выполняется, так как узел р[z] теперь черный.
Теперь покажем, что случаи 2 и 3 сохраняют инвариант цикла.
а) В случае 2 выполняется присвоение, после которого z указывает на красный узел р[z]. Никаких других изменений z или его цвета в случаях 2 и 3 не выполняется.
б) В случае 3 узел р[z] делается черным, так что если р[z] в начале следующей итерации является корнем, то этот корень - черный.
в) Как и в случае 1, в случае 2 и 3 свойства 1, 3 и 5 сохраняются. Поскольку узел z в случаях 2 и 3 не является корнем, нарушение свойства 2 невозможно. Случаи 2 и 3 не могут приводить к нарушению свойства 2, поскольку при повороте в случае 3 красный узел становится дочерним по отношению к черному.
Таким образом, случаи 2 и 3 приводят к коррекции нарушения свойства 4, при этом не внося никаких новых нарушений красно-черных свойств.
Показав, что при любой итерации инвариант цикла сохраняется, мы тем самым показали, что процедура RB_Insert_Fixup корректно восстанавливает красно-черные свойства дерева [3].
Рис. 12. Случаи 2 и 3 процедуры RB_Insert_Fixup