-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRedBlackTree.java
More file actions
458 lines (414 loc) · 15.5 KB
/
Copy pathRedBlackTree.java
File metadata and controls
458 lines (414 loc) · 15.5 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
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Stack;
// --== CS400 File Header Information ==--
// Name: Ethan Donald Knifsend
// Email: knifsend@wisc.edu
// Team: FB
// TA: Daniel Finer
// Lecturer: Gary Dahl
// Notes to Grader: <optional extra notes>
/**
* 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;
}
/**
* @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++;
else
throw new IllegalArgumentException("This RedBlackTree already contains that value.");
root.isBlack = true;
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
* @see enforceRBTreePropertiesAfterInsert()
*/
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);
}
}
/**
* Private helper that ensures there are no violations present. If there are, it resolves them.
*
* @param newNode Node<T> that was added
* @see caseType()
* @see rotate()
* @see checkIsBlack
*/
private void enforceRBTreePropertiesAfterInsert(Node<T> newNode) {
if (!newNode.equals(root) && !newNode.isBlack && !checkIsBlack(newNode.parent)) {
if (caseType(newNode) == 1) {
newNode.parent.isBlack = true;
newNode.parent.parent.isBlack = false;
rotate(newNode.parent, newNode.parent.parent);
} else if (caseType(newNode) == 2) {
rotate(newNode, newNode.parent);
newNode.isBlack = true;
newNode.parent.isBlack = false;
rotate(newNode, newNode.parent);
} else {
newNode.parent.isBlack = true;
newNode.parent.parent.isBlack = false;
if (newNode.parent.isLeftChild()) {
if (newNode.parent.parent.rightChild != null) {
newNode.parent.parent.rightChild.isBlack = true;
}
} else {
if (newNode.parent.parent.leftChild != null) {
newNode.parent.parent.leftChild.isBlack = true;
}
}
enforceRBTreePropertiesAfterInsert(newNode.parent.parent);
}
}
}
/**
* Private helper method that checks which case type the violation is
*
* @param newNode Node<T> that was added and is causing a violation
* @return int representing the case type
* @see checkIsBlack
*/
private int caseType(Node<T> newNode) {
if (newNode.parent.isLeftChild()) {
if (checkIsBlack(newNode.parent.parent.rightChild)) {
if (newNode.isLeftChild()) {
return 1;
} else {
return 2;
}
} else {
return 3;
}
} else {
if (checkIsBlack(newNode.parent.parent.leftChild)) {
if (!newNode.isLeftChild()) {
return 1;
} else {
return 2;
}
} else {
return 3;
}
}
}
/**
* Private helper method that checks if the node is black (either null or isBlack == true)
*/
private boolean checkIsBlack(Node<T> newNode) {
if (newNode == null || newNode.isBlack == true) {
return true;
} else {
return false;
}
}
/**
* 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> child, Node<T> parent) throws IllegalArgumentException {
if (child.parent.equals(parent) && child.isLeftChild()) {
rotateRight(child, parent);
} else if (child.parent.equals(parent) && parent.rightChild.equals(child)) {
rotateLeft(child, parent);
} else {
throw new IllegalArgumentException("The nodes are unrelated");
}
// TODO: Implement this method.
}
private void rotateRight(Node<T> child, Node<T> parent) {
parent.leftChild = child.rightChild;
if (child.rightChild != null) {
child.rightChild.parent = parent;
}
child.rightChild = parent;
setParent(child, parent);
}
private void rotateLeft(Node<T> child, Node<T> parent) {
parent.rightChild = child.leftChild;
if (child.leftChild != null) {
child.leftChild.parent = parent;
}
child.leftChild = parent;
setParent(child, parent);
}
private void setParent(Node<T> temp, Node<T> parent) {
if (parent.equals(root) || parent.parent == null) {
temp.parent = null;
root = temp;
} else if (parent.isLeftChild()) {
temp.parent = parent.parent;
parent.parent.leftChild = temp;
} else {
temp.parent = parent.parent;
parent.parent.rightChild = temp;
}
parent.parent = temp;
}
/**
* 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();
}
}