Skip to content

Commit 162c345

Browse files
committed
Exchanged old red-black tree implementation with new one, also updated redmea
1 parent 8da897d commit 162c345

File tree

4 files changed

+930
-95
lines changed

4 files changed

+930
-95
lines changed

Red-Black Tree/README.markdown

Lines changed: 164 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,189 @@
11
# Red-Black Tree
22

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.
44

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:
226

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:
377
```
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
4615
```
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:
4717

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
7419

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
7625

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]
8028

81-
Reaching the end we have successfully made the tree valid again.
29+
## Methods
8230

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
8442

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]
8944

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
9446

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.
9648

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
9850

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).
10072

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
10274

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]
10488
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]
106100
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]
108112
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]
110136
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]
112148
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]
117162
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]
119174
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.*
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//: Playground - noun: a place where people can play
2+
// Test for the RedBlackTree implementation provided in the Source folder of this Playground
3+
import Foundation
4+
5+
let redBlackTree = RedBlackTree<Double>()
6+
7+
let randomMax = Double(0x10000000)
8+
var values = [Double]()
9+
for _ in 0..<1000 {
10+
let value = Double(arc4random()) / randomMax
11+
values.append(value)
12+
redBlackTree.insert(key: value)
13+
}
14+
redBlackTree.verify()
15+
16+
for _ in 0..<1000 {
17+
let rand = arc4random_uniform(UInt32(values.count))
18+
let index = Int(rand)
19+
let value = values.remove(at: index)
20+
redBlackTree.delete(key: value)
21+
}
22+
redBlackTree.verify()

0 commit comments

Comments
 (0)