|
1 | 1 | # Red-Black Tree
|
2 | 2 |
|
3 |
| -A Red-Black tree is a special version of a [Binary Search Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Binary%20Search%20Tree). Binary search trees(BSTs) can become unbalanced after a lot of insertions/deletions. The only difference between a node from a BST and a Red-Black Tree(RBT) is that RBT nodes have a color property added to them which can either be red or black. A RBT rebalances itself by making sure the following properties hold: |
| 3 | +A red-black tree (RBT) is a balanced version of a [Binary Search Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Binary%20Search%20Tree) guaranteeing that the basic operations (search, predecessor, successor, minimum, maximum, insert and delete) have a logarithmic worst case performance. |
4 | 4 |
|
5 |
| -## Properties |
6 |
| -1. A node is either red or black |
7 |
| -2. The root is always black |
8 |
| -3. All leaves are black |
9 |
| -4. If a node is red, both of its children are black |
10 |
| -5. Every path to a leaf contains the same number of black nodes (The amount of black nodes met when going down a RBT is called the black-depth of the tree.) |
11 |
| - |
12 |
| -## Methods |
13 |
| -* `insert(_ value: T)` inserts the value into the tree |
14 |
| -* `insert(_ values: [T])` inserts an array of values into the tree |
15 |
| -* `delete(_ value: T)` deletes the value from the tree |
16 |
| -* `find(_ value: T) -> RBTNode<T>` looks for a node in the tree with given value and returns it |
17 |
| -* `minimum(n: RBTNode<T>) -> RBTNode<T>` looks for the maximum value of a subtree starting at the given node |
18 |
| -* `maximum(n: RBTNode<T>) -> RBTNode<T>` looks for the minimum value of a subtree starting at the given node |
19 |
| -* `func verify()` verifies if the tree is still valid. Prints warning messages if this is not the case |
20 |
| - |
21 |
| -## Rotation |
| 5 | +Binary search trees (BSTs) have the disadvantage that they can become unbalanced after some insert or delete operations. In the worst case, this could lead to a tree where the nodes build a linked list as shown in the following example: |
22 | 6 |
|
23 |
| -In order to rebalance their nodes RBTs use an operation known as rotation. You can either left or right rotate a node and its child. Rotating a node and its child swaps the nodes and interchanges their subtrees. Left rotation swaps the parent node with its right child. while right rotation swaps the parent node with its left child. |
24 |
| - |
25 |
| -Left rotation: |
26 |
| -``` |
27 |
| -before left rotating p after left rotating p |
28 |
| - p b |
29 |
| - / \ / \ |
30 |
| - a b -> p n |
31 |
| - / \ / \ / \ |
32 |
| -n n n n a n |
33 |
| - / \ |
34 |
| - n n |
35 |
| -``` |
36 |
| -Right rotation: |
37 | 7 | ```
|
38 |
| -before right rotating p after right rotating p |
39 |
| - p a |
40 |
| - / \ / \ |
41 |
| - a b -> n p |
42 |
| - / \ / \ / \ |
43 |
| -n n n n n b |
44 |
| - / \ |
45 |
| - n n |
| 8 | +a |
| 9 | + \ |
| 10 | + b |
| 11 | + \ |
| 12 | + c |
| 13 | + \ |
| 14 | + d |
46 | 15 | ```
|
| 16 | +To prevent this issue, RBTs perform rebalancing operations after an insert or delete and store an additional color property at each node which can either be red or black. After each operation a RBT satisfies the following properties: |
47 | 17 |
|
48 |
| -## Insertion |
49 |
| - |
50 |
| -We create a new node with the value to be inserted into the tree. The color of this new node is always red. |
51 |
| -We perform a standard BST insert with this node. Now the three might not be a valid RBT anymore. |
52 |
| -We now go through several insertion steps in order to make the tree valid again. We call the just inserted node n. |
53 |
| - |
54 |
| -**Step 1**: We check if n is the rootNode, if so we paint it black and we are done. If not we go to Step 2. |
55 |
| - |
56 |
| -We now know that n at least has a parent as it is not the rootNode. |
57 |
| - |
58 |
| -**Step 2**: We check if the parent of n is black if so we are done. If not we go to Step 3. |
59 |
| - |
60 |
| -We now know the parent is also not the root node as the parent is red. Thus n also has a grandparent and thus also an uncle as every node has two children. This uncle may however be a nullLeaf |
61 |
| - |
62 |
| -**Step 3**: We check if n's uncle is red. If not we go to Step 4. If n's uncle is indeed red we recolor uncle and parent to black and n's grandparent to red. We now go back to step 1 performing the same logic on n's grandparent. |
63 |
| - |
64 |
| -From here there are four cases: |
65 |
| -- **The left left case.** n's parent is the left child of its parent and n is the left child of its parent. |
66 |
| -- **The left right case** n's parent is the left child of its parent and n is the right child of its parent. |
67 |
| -- **The right right case** n's parent is the right child of its parent and n is the right child of its parent. |
68 |
| -- **The right left case** n's parent is the right child of its parent and n is the left child of its parent. |
69 |
| - |
70 |
| -**Step 4**: checks if either the **left right** case or the **right left** case applies to the current situation. |
71 |
| - - If we find the **left right case**, we left rotate n's parent and go to Step 5 while setting n to n's parent. (This transforms the **left right** case into the **left left** case) |
72 |
| - - If we find the **right left case**, we right rotate n's parent and go to Step 5 while setting n to n's parent. (This transforms the **right left** case into the **right right** case) |
73 |
| - - If we find neither of these two we proceed to Step 5. |
| 18 | +## Properties |
74 | 19 |
|
75 |
| -n's parent is now red, while n's grandparent is black. |
| 20 | +1. Every node is either red or black |
| 21 | +2. The root is black |
| 22 | +3. Every leaf (nullLeaf) is black |
| 23 | +4. If a node is red, then both its children are black |
| 24 | +5. For each node, all paths from the node to descendant leaves contain the same number of black nodes |
76 | 25 |
|
77 |
| -**Step 5**: We swap the colors of n's parent and grandparent. |
78 |
| - - We either right rotate n's grandparent in case of the **left left** case |
79 |
| - - Or we left rotate n's grandparent in case of the **right right** case |
| 26 | +Property 5 includes the definition of the black-height of a node x, bh(x), which is the number of black nodes on a path from this node down to a leaf not counting the node itself. |
| 27 | +From [CLRS] |
80 | 28 |
|
81 |
| -Reaching the end we have successfully made the tree valid again. |
| 29 | +## Methods |
82 | 30 |
|
83 |
| -# Deletion |
| 31 | +Nodes: |
| 32 | +* `nodeX.getSuccessor()` Returns the inorder successor of nodeX |
| 33 | +* `nodeX.minimum()` Returns the node with the minimum key of the subtree of nodeX |
| 34 | +* `nodeX.maximum()` Returns the node with the maximum key of the subtree of nodeX |
| 35 | +Tree: |
| 36 | +* `search(input:)` Returns the node with the given key value |
| 37 | +* `minValue()` Returns the minimum key value of the whole tree |
| 38 | +* `maxValue()` Returns the maximum key value of the whole tree |
| 39 | +* `insert(key:)` Inserts the key value into the tree |
| 40 | +* `delete(key:)` Delete the node with the respective key value from the tree |
| 41 | +* `verify()` Verifies that the given tree fulfills the red-black tree properties |
84 | 42 |
|
85 |
| -Deletion is a little more complicated than insertion. In the following we call del the node to be deleted. |
86 |
| -First we try and find the node to be deleted using find() |
87 |
| -we send the result of find to del. |
88 |
| -We now go through the following steps in order to delete the node del. |
| 43 | +The rotation, insertion and deletion algorithms are implemented based on the pseudo-code provided in [CLRS] |
89 | 44 |
|
90 |
| -First we do a few checks: |
91 |
| -- Is del the rootNode if so set the root to a nullLeaf and we are done. |
92 |
| -- If del has 2 nullLeaf children and is red. We check if del is either a left or a right child and set del's parent left or right to a nullLeaf. |
93 |
| -- If del has two non nullLeaf children we look for the maximum value in del's left subtree. We set del's value to this maximum value and continue to instead delete this maximum node. Which we will now call del. |
| 45 | +## Implementation Details |
94 | 46 |
|
95 |
| -Because of these checks we now know that del has at most 1 non nullLeaf child. It has either two nullLeaf children or one nullLeaf and one regular red child. (The child is red otherwise the black-depth wouldn't be the same for every leaf) |
| 47 | +For convenience, all nil-pointers to children or the parent (except the parent of the root) of a node are exchanged with a nullLeaf. This is an ordinary node object, like all other nodes in the tree, but with a black color, and in this case a nil value for its children, parent and key. Therefore, an empty tree consists of exactly one nullLeaf at the root. |
96 | 48 |
|
97 |
| -We now call the non nullLeaf child of del, child. If del has two nullLeaf children child will be a nullLeaf. This means child can either be a nullLeaf or a red node. |
| 49 | +## Rotation |
98 | 50 |
|
99 |
| -We now have three options |
| 51 | +Left rotation (around x): |
| 52 | +Assumes that x.rightChild y is not a nullLeaf, rotates around the link from x to y, makes y the new root of the subtree with x as y's left child and y's left child as x's right child, where n = a node, [n] = a subtree |
| 53 | +``` |
| 54 | + | | |
| 55 | + x y |
| 56 | + / \ ~> / \ |
| 57 | + [A] y x [C] |
| 58 | + / \ / \ |
| 59 | + [B] [C] [A] [B] |
| 60 | +``` |
| 61 | +Right rotation (around y): |
| 62 | +Assumes that y.leftChild x is not a nullLeaf, rotates around the link from y to x, makes x the new root of the subtree with y as x's right child and x's right child as y's left child, where n = a node, [n] = a subtree |
| 63 | +``` |
| 64 | + | | |
| 65 | + x y |
| 66 | + / \ <~ / \ |
| 67 | + [A] y x [C] |
| 68 | + / \ / \ |
| 69 | + [B] [C] [A] [B] |
| 70 | +``` |
| 71 | +As rotation is a local operation only exchanging pointers it's runtime is O(1). |
100 | 72 |
|
101 |
| -- If del is red its child is a nullLeaf and we can just delete it as it doesn't change the black-depth of the tree. We are done |
| 73 | +## Insertion |
102 | 74 |
|
103 |
| -- If child is red we recolor it black and child's parent to del's parent. del is now deleted and we are done. |
| 75 | +We create a node with the given key and set its color to red. Then we insert it into the the tree by performing a standard insert into a BST. After this, the tree might not be a valid RBT anymore, so we fix the red-black properties by calling the insertFixup algorithm. |
| 76 | +The only violation of the red-black properties occurs at the inserted node z and its parent. Either both are red, or the parent does not exist (so there is a violation since the root must be black). |
| 77 | +We have three distinct cases: |
| 78 | +**Case 1:** The uncle of z is red. If z.parent is left child, z's uncle is z.grandparent's right child. If this is the case, we recolor and move z to z.grandparent, then we check again for this new z. In the following cases, we denote a red node with (x) and a black node with {x}, p = parent, gp = grandparent and u = uncle |
| 79 | +``` |
| 80 | + | | |
| 81 | + {gp} (newZ) |
| 82 | + / \ ~> / \ |
| 83 | + (p) (u) {p} {u} |
| 84 | + / \ / \ / \ / \ |
| 85 | + (z) [C] [D] [E] (z) [C] [D] [E] |
| 86 | + / \ / \ |
| 87 | +[A] [B] [A] [B] |
104 | 88 |
|
105 |
| -- Both del and child are black we go through |
| 89 | +``` |
| 90 | +**Case 2a:** The uncle of z is black and z is right child. Here, we move z upwards, so z's parent is the newZ and then we rotate around this newZ. After this, we have Case 2b. |
| 91 | +``` |
| 92 | + | | |
| 93 | + {gp} {gp} |
| 94 | + / \ ~> / \ |
| 95 | + (p) {u} (z) {u} |
| 96 | + / \ / \ / \ / \ |
| 97 | + [A] (z) [D] [E] (newZ) [C] [D] [E] |
| 98 | + / \ / \ |
| 99 | + [B] [C] [A] [B] |
106 | 100 |
|
107 |
| -If both del and child are black we introduce a new variable sibling. Which is del's sibling. We also replace del with child and recolor it doubleBlack. del is now deleted and child and sibling are siblings. |
| 101 | +``` |
| 102 | +**Case 2b:** The uncle of z is black and z is left child. In this case, we recolor z.parent to black and z.grandparent to red. Then we rotate around z's grandparent. Afterwards we have valid red-black tree. |
| 103 | +``` |
| 104 | + | | |
| 105 | + {gp} {p} |
| 106 | + / \ ~> / \ |
| 107 | + (p) {u} (z) (gp) |
| 108 | + / \ / \ / \ / \ |
| 109 | + (z) [C] [D] [E] [A] [B] [C] {u} |
| 110 | + / \ / \ |
| 111 | +[A] [B] [D] [E] |
108 | 112 |
|
109 |
| -We now have to go through a lot of steps in order to remove this doubleBlack color. Which are hard to explain in text without just writing the full code in text. This is due the many possible combination of nodes and colors. The code is commented, but if you don't quite understand please leave me a message. Also part of the deletion is described by the final link in the Also See section. |
| 113 | +``` |
| 114 | +Running time of this algorithm: |
| 115 | +* Only case 1 may repeat, but this only h/2 steps, where h is the height of the tree |
| 116 | +* Case 2a -> Case 2b -> red-black tree |
| 117 | +* Case 2b -> red-black tree |
| 118 | +As we perform case 1 at most O(log n) times and all other cases at most once, we have O(log n) recolorings and at most 2 rotations. |
| 119 | +The overall runtime of insert is O(log n). |
| 120 | + |
| 121 | +## Deletion |
| 122 | + |
| 123 | +We search for the node with the given key, and if it exists we delete it by performing a standard delete from a BST. If the deleted nodes' color was red everything is fine, otherwise red-black properties may be violated so we call the fixup procedure deleteFixup. |
| 124 | +Violations can be that the parent and child of the deleted node x where red, so we now have two adjacent red nodes, or if we deleted the root, the root could now be red, or the black height property is violated. |
| 125 | +We have 4 cases: We call deleteFixup on node x |
| 126 | +**Case 1:** The sibling of x is red. The sibling is the other child of x's parent, which is not x itself. In this case, we recolor the parent of x and x.sibling then we left rotate around x's parent. In the following cases s = sibling of x, (x) = red, {x} = black, |x| = red/black. |
| 127 | +``` |
| 128 | + | | |
| 129 | + {p} {s} |
| 130 | + / \ ~> / \ |
| 131 | + {x} (s) (p) [D] |
| 132 | + / \ / \ / \ |
| 133 | + [A] [B] [C] [D] {x} [C] |
| 134 | + / \ |
| 135 | + [A] [B] |
110 | 136 |
|
111 |
| -## Also see |
| 137 | +``` |
| 138 | +**Case 2:** The sibling of x is black and has two black children. Here, we recolor x.sibling to red, move x upwards to x.parent and check again for this newX. |
| 139 | +``` |
| 140 | + | | |
| 141 | + |p| |newX| |
| 142 | + / \ ~> / \ |
| 143 | + {x} {s} {x} (s) |
| 144 | + / \ / \ / \ / \ |
| 145 | + [A] [B] {l} {r} [A] [B] {l} {r} |
| 146 | + / \ / \ / \ / \ |
| 147 | + [C][D][E][F] [C][D][E][F] |
112 | 148 |
|
113 |
| -* [Wikipedia](https://en.wikipedia.org/wiki/Red–black_tree) |
114 |
| -* [GeeksforGeeks - introduction](http://www.geeksforgeeks.org/red-black-tree-set-1-introduction-2/) |
115 |
| -* [GeeksforGeeks - insertion](http://www.geeksforgeeks.org/red-black-tree-set-2-insert/) |
116 |
| -* [GeeksforGeeks - deletion](http://www.geeksforgeeks.org/red-black-tree-set-3-delete-2/) |
| 149 | +``` |
| 150 | +**Case 3:** The sibling of x is black with one black child to the right. In this case, we recolor the sibling to red and sibling.leftChild to black, then we right rotate around the sibling. After this we have case 4. |
| 151 | +``` |
| 152 | + | | |
| 153 | + |p| |p| |
| 154 | + / \ ~> / \ |
| 155 | + {x} {s} {x} {l} |
| 156 | + / \ / \ / \ / \ |
| 157 | + [A] [B] (l) {r} [A] [B] [C] (s) |
| 158 | + / \ / \ / \ |
| 159 | + [C][D][E][F] [D]{e} |
| 160 | + / \ |
| 161 | + [E] [F] |
117 | 162 |
|
118 |
| -Important to note is that GeeksforGeeks doesn't mention a few deletion cases that do occur. The code however does implement these. |
| 163 | +``` |
| 164 | +**Case 4:** The sibling of x is black with one red child to the right. Here, we recolor the sibling to the color of x.parent and x.parent and sibling.rightChild to black. Then we left rotate around x.parent. After this operation we have a valid red-black tree. Here, ||x|| denotes that x can have either color red or black, but that this can be different to |x| color. This is important, as s gets the same color as p. |
| 165 | +``` |
| 166 | + | | |
| 167 | + ||p|| ||s|| |
| 168 | + / \ ~> / \ |
| 169 | + {x} {s} {p} {r} |
| 170 | + / \ / \ / \ / \ |
| 171 | + [A] [B] |l| (r) {x} |l| [E] [F] |
| 172 | + / \ / \ / \ / \ |
| 173 | + [C][D][E][F] [A][B][C][D] |
119 | 174 |
|
120 |
| -*Written for Swift Algorithm Club by Jaap Wijnen. Updated from Ashwin Raghuraman's contribution.* |
| 175 | +``` |
| 176 | +Running time of this algorithm: |
| 177 | +* Only case 2 can repeat, but this only h many times, where h is the height of the tree |
| 178 | +* Case 1 -> case 2 -> red-black tree |
| 179 | + Case 1 -> case 3 -> case 4 -> red-black tree |
| 180 | + Case 1 -> case 4 -> red-black tree |
| 181 | +* Case 3 -> case 4 -> red-black tree |
| 182 | +* Case 4 -> red-black tree |
| 183 | +As we perform case 2 at most O(log n) times and all other steps at most once, we have O(log n) recolorings and at most 3 rotations. |
| 184 | +The overall runtime of delete is O(log n). |
| 185 | + |
| 186 | +## Resources: |
| 187 | +[CLRS] T. Cormen, C. Leiserson, R. Rivest, and C. Stein. "Introduction to Algorithms", Third Edition. 2009 |
| 188 | + |
| 189 | +*Written for Swift Algorithm Club by Ute Schiehlen. Updated from Jaap Wijnen and Ashwin Raghuraman's contributions.* |
0 commit comments