-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRedBlackTree.java
More file actions
419 lines (393 loc) · 17.3 KB
/
RedBlackTree.java
File metadata and controls
419 lines (393 loc) · 17.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
// --== CS400 File Header Information ==--
// Name: Nitit Jongsawatsataporn
// Email: jongsawatsat@wisc.edu
// Team: CG
// TA: Xi
// Lecturer: Florian Heimerl
// Notes to Grader: <optional extra notes>
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Stack;
// import org.junit.Test;
// import static org.junit.Assert.*;
/**
* Red-Black Tree implementation with a Node inner class for representing
* the nodes of the tree. Currently, this implements a Binary Search Tree that
* we will turn into a red black tree by modifying the insert functionality.
* In this activity, we will start with implementing rotations for the binary
* search tree insert algorithm. You can use this class' insert method to build
* a regular binary search tree, and its toString method to display a level-order
* traversal of the tree.
*/
public class RedBlackTree<T extends Comparable<T>> implements SortedCollectionInterface<T> {
/**
* This class represents a node holding a single value within a binary tree
* the parent, left, and right child references are always maintained.
*/
protected static class Node<T> {
public T data;
public Node<T> parent; // null for root node
public Node<T> leftChild;
public Node<T> rightChild;
public boolean isBlack;
public Node(T data) {
this.data = data;
isBlack = false;
}
/**
* @return true when this node has a parent and is the left child of
* that parent, otherwise return false
*/
public boolean isLeftChild() {
return parent != null && parent.leftChild == this;
}
/**
* This method performs a level order traversal of the tree rooted
* at the current node. The string representations of each data value
* within this tree are assembled into a comma separated string within
* brackets (similar to many implementations of java.util.Collection).
* Note that the Node's implementation of toString generates a level
* order traversal. The toString of the RedBlackTree class below
* produces an inorder traversal of the nodes / values of the tree.
* This method will be helpful as a helper for the debugging and testing
* of your rotation implementation.
* @return string containing the values of this tree in level order
*/
@Override
public String toString() {
String output = "[";
LinkedList<Node<T>> q = new LinkedList<>();
q.add(this);
while(!q.isEmpty()) {
Node<T> next = q.removeFirst();
if(next.leftChild != null) q.add(next.leftChild);
if(next.rightChild != null) q.add(next.rightChild);
output += next.data.toString();
if(!q.isEmpty()) output += ", ";
}
return output + "]";
}
}
protected Node<T> root; // reference to root node of tree, null when empty
protected int size = 0; // the number of values in the tree
/**
* Performs a naive insertion into a binary search tree: adding the input
* data value to a new node in a leaf position within the tree. After
* this insertion, no attempt is made to restructure or balance the tree.
* This tree will not hold null references, nor duplicate data values.
* @param data to be added into this binary search tree
* @return true if the value was inserted, false if not
* @throws NullPointerException when the provided data argument is null
* @throws IllegalArgumentException when the newNode and subtree contain
* equal data references
*/
@Override
public boolean insert(T data) throws NullPointerException, IllegalArgumentException {
// null references cannot be stored within this tree
if(data == null) throw new NullPointerException(
"This RedBlackTree cannot store null references.");
Node<T> newNode = new Node<>(data);
if(root == null) { root = newNode; size++; root.isBlack = true; return true; } // add first node to an empty tree
else{
boolean returnValue = insertHelper(newNode,root); // recursively insert into subtree
if (returnValue) { size++; root.isBlack = true; }
else throw new IllegalArgumentException(
"This RedBlackTree already contains that value.");
return returnValue;
}
}
/**
* Recursive helper method to find the subtree with a null reference in the
* position that the newNode should be inserted, and then extend this tree
* by the newNode in that position.
* @param newNode is the new node that is being added to this tree
* @param subtree is the reference to a node within this tree which the
* newNode should be inserted as a descenedent beneath
* @return true is the value was inserted in subtree, false if not
*/
private boolean insertHelper(Node<T> newNode, Node<T> subtree) {
int compare = newNode.data.compareTo(subtree.data);
// do not allow duplicate values to be stored within this tree
if(compare == 0) return false;
// store newNode within left subtree of subtree
else if(compare < 0) {
if(subtree.leftChild == null) { // left subtree empty, add here
subtree.leftChild = newNode;
newNode.parent = subtree;
enforceRBTreePropertiesAfterInsert(newNode);
return true;
// otherwise continue recursive search for location to insert
} else return insertHelper(newNode, subtree.leftChild);
}
// store newNode within the right subtree of subtree
else {
if(subtree.rightChild == null) { // right subtree empty, add here
subtree.rightChild = newNode;
newNode.parent = subtree;
enforceRBTreePropertiesAfterInsert(newNode);
return true;
// otherwise continue recursive search for location to insert
} else return insertHelper(newNode, subtree.rightChild);
}
}
/**
* Performs the rotation operation on the provided nodes within this tree.
* When the provided child is a leftChild of the provided parent, this
* method will perform a right rotation. When the provided child is a
* rightChild of the provided parent, this method will perform a left rotation.
* When the provided nodes are not related in one of these ways, this method
* will throw an IllegalArgumentException.
* @param child is the node being rotated from child to parent position
* (between these two node arguments)
* @param parent is the node being rotated from parent to child position
* (between these two node arguments)
* @throws IllegalArgumentException when the provided child and parent
* node references are not initially (pre-rotation) related that way
*/
private void rotate(Node<T> C, Node<T> P) throws IllegalArgumentException {
if (P == null || (P.leftChild != C && P.rightChild != C))
throw new IllegalArgumentException("The parent-child does not have direct relation"+ C.data + P.data + C.parent.data + P.leftChild + P.rightChild);
if (P.leftChild == C) {
//Left Rotation
if(P.parent != null) {
if(P.isLeftChild())
P.parent.leftChild = C;
else
P.parent.rightChild = C;
}
//Now set parent of child
C.parent = P.parent;
//Set the rest in the following order
P.leftChild = C.rightChild;
if(C.rightChild != null)
C.rightChild.parent = P;
P.parent = C;
C.rightChild = P;
}
else {
if(P.parent != null) {
if(P.isLeftChild())
P.parent.leftChild = C;
else
P.parent.rightChild = C;
}
C.parent = P.parent;
P.rightChild = C.leftChild;
if(C.leftChild != null)
C.leftChild.parent = P;
P.parent = C;
C.leftChild = P;
}
if(C.parent == null)
root = C;
}
private void enforceRBTreePropertiesAfterInsert(Node<T> redNode) {
//Deal the root case
if(redNode.parent == null) {
redNode.isBlack = true;
return;
}
//Now, we can be sure that it's not a root node
if(redNode.parent.isBlack) {
//This is done. Nothing to do
return;
}
else {
//Then we have to check parent-siblings relation
//Note: the root always black so parent is not a root
Node<T> P = redNode.parent;
Node<T> S;
if(P.isLeftChild())
S = P.parent.rightChild;
else
S = P.parent.leftChild;
if(S == null || S.isBlack) {
//Now we have 4 subcases
if(P.isLeftChild()) {
if(redNode.isLeftChild()) {
//Rotate up the parent
rotate(P, P.parent);
//Recolor
redNode.isBlack = false;
P.rightChild.isBlack = false;
P.isBlack = true;
}
else {
//Rotate the son first
rotate(redNode, P);
rotate(redNode, redNode.parent);
redNode.leftChild.isBlack = false;
redNode.rightChild.isBlack = false;
redNode.isBlack = true;
}
}
else {
if(redNode.isLeftChild()) {
//Rotate the son first
rotate(redNode, P);
rotate(redNode, redNode.parent);
redNode.leftChild.isBlack = false;
redNode.rightChild.isBlack = false;
redNode.isBlack = true;
}
else {
//Rotate up the parent
rotate(P, P.parent);
//Recolor
redNode.isBlack = false;
P.leftChild.isBlack = false;
P.isBlack = true;
}
}
}
else {
//Recolor is enough
P.isBlack = true;
S.isBlack = true;
P.parent.isBlack = false; //If it is root, the insert will fix it later
//Fix the grandparent
enforceRBTreePropertiesAfterInsert(P.parent);
}
}
}
/**
* Get the size of the tree (its number of nodes).
* @return the number of nodes in the tree
*/
@Override
public int size() {
return size;
}
/**
* Method to check if the tree is empty (does not contain any node).
* @return true of this.size() return 0, false if this.size() > 0
*/
@Override
public boolean isEmpty() {
return this.size() == 0;
}
/**
* Checks whether the tree contains the value *data*.
* @param data the data value to test for
* @return true if *data* is in the tree, false if it is not in the tree
*/
@Override
public boolean contains(T data) {
// null references will not be stored within this tree
if(data == null) throw new NullPointerException(
"This RedBlackTree cannot store null references.");
return this.containsHelper(data, root);
}
/**
* Recursive helper method that recurses through the tree and looks
* for the value *data*.
* @param data the data value to look for
* @param subtree the subtree to search through
* @return true of the value is in the subtree, false if not
*/
private boolean containsHelper(T data, Node<T> subtree) {
if (subtree == null) {
// we are at a null child, value is not in tree
return false;
} else {
int compare = data.compareTo(subtree.data);
if (compare < 0) {
// go left in the tree
return containsHelper(data, subtree.leftChild);
} else if (compare > 0) {
// go right in the tree
return containsHelper(data, subtree.rightChild);
} else {
// we found it :)
return true;
}
}
}
/**
* Returns an iterator over the values in in-order (sorted) order.
* @return iterator object that traverses the tree in in-order sequence
*/
@Override
public Iterator<T> iterator() {
// use an anonymous class here that implements the Iterator interface
// we create a new on-off object of this class everytime the iterator
// method is called
return new Iterator<T>() {
// a stack and current reference store the progress of the traversal
// so that we can return one value at a time with the Iterator
Stack<Node<T>> stack = null;
Node<T> current = root;
/**
* The next method is called for each value in the traversal sequence.
* It returns one value at a time.
* @return next value in the sequence of the traversal
* @throws NoSuchElementException if there is no more elements in the sequence
*/
public T next() {
// if stack == null, we need to initialize the stack and current element
if (stack == null) {
stack = new Stack<Node<T>>();
current = root;
}
// go left as far as possible in the sub tree we are in until we hit a null
// leaf (current is null), pushing all the nodes we fund on our way onto the
// stack to process later
while (current != null) {
stack.push(current);
current = current.leftChild;
}
// as long as the stack is not empty, we haven't finished the traversal yet;
// take the next element from the stack and return it, then start to step down
// its right subtree (set its right sub tree to current)
if (!stack.isEmpty()) {
Node<T> processedNode = stack.pop();
current = processedNode.rightChild;
return processedNode.data;
} else {
// if the stack is empty, we are done with our traversal
throw new NoSuchElementException("There are no more elements in the tree");
}
}
/**
* Returns a boolean that indicates if the iterator has more elements (true),
* or if the traversal has finished (false)
* @return boolean indicating whether there are more elements / steps for the traversal
*/
public boolean hasNext() {
// return true if we either still have a current reference, or the stack
// is not empty yet
return !(current == null && (stack == null || stack.isEmpty()) );
}
};
}
/**
* This method performs an inorder traversal of the tree. The string
* representations of each data value within this tree are assembled into a
* comma separated string within brackets (similar to many implementations
* of java.util.Collection, like java.util.ArrayList, LinkedList, etc).
* Note that this RedBlackTree class implementation of toString generates an
* inorder traversal. The toString of the Node class class above
* produces a level order traversal of the nodes / values of the tree.
* @return string containing the ordered values of this tree (in-order traversal)
*/
@Override
public String toString() {
// use the inorder Iterator that we get by calling the iterator method above
// to generate a string of all values of the tree in (ordered) in-order
// traversal sequence
Iterator<T> treeNodeIterator = this.iterator();
StringBuffer sb = new StringBuffer();
sb.append("[ ");
if (treeNodeIterator.hasNext())
sb.append(treeNodeIterator.next());
while (treeNodeIterator.hasNext()) {
T data = treeNodeIterator.next();
sb.append(", ");
sb.append(data.toString());
}
sb.append(" ]");
return sb.toString();
}
}