27. Linked Binary Search Trees
Given the way the binary search tree ideas were presented, a linked implementation may feel like an obvious choice
Though, there is nothing preventing an array based implementation
27.1. Constructors
6public class LinkedBinarySearchTree<T extends Comparable<? super T>> implements BinarySearchTree<T> {
7
8 private int size;
9 private Node<T> root;
10
11 public LinkedBinarySearchTree() {
12 root = null;
13 size = 0;
14 }
Like the other implementations, this class
implements
the interfaceThe data structure is to be generic
Like the sorted bag, since it is concerned with the ordering, ensure that the objects are comparable
There is a single constructor to create an empty binary search tree
27.2. Static Node Class
Notice the use of
Node<T> root
Like the linked stack and linked queue, this linked binary search tree will make use of nodes
To keep things clean, a nested node class is used
462 /**
463 * A node class for a linked binary tree structure. Each node contains a nullable reference to data of type T, and a
464 * reference to the left and right child nodes, which may be null references.
465 *
466 * @param <T> Type of the data being stored in the node
467 */
468 private static class Node<T> {
469 private T data;
470 private Node<T> left;
471 private Node<T> right;
472
473 private Node() {
474 this(null);
475 }
476
477 private Node(T data) {
478 this.data = data;
479 this.left = null;
480 this.right = null;
481 }
482
483 private T getData() {
484 return data;
485 }
486
487 private void setData(T data) {
488 this.data = data;
489 }
490
491 private Node<T> getLeft() {
492 return left;
493 }
494
495 private void setLeft(Node<T> left) {
496 this.left = left;
497 }
498
499 private Node<T> getRight() {
500 return right;
501 }
502
503 private void setRight(Node<T> right) {
504 this.right = right;
505 }
506 }
Things of note for this node class
This is a binary tree node, thus it has a left and right reference for the left and right subtrees
This nested static class is contained within the
LinkedBinarySearchTree
class
27.3. Add to Binary Search Tree
18 @Override
19 public boolean add(T element) {
20 root = add(element, root);
21 size++;
22 return true;
23 }
24
25 /**
26 * Helper add method for enabling recursive add. This method ensures that elements are added in the proper order for
27 * a Binary Search Tree.
28 *
29 * @param element Element to be added to the tree
30 * @param current Current root of subtree
31 * @return Node being assigned as left/right child
32 */
33 private Node<T> add(T element, Node<T> current) {
34 if (current == null) {
35 return new Node<>(element);
36 }
37 if (current.getData().compareTo(element) > 0) {
38 current.setLeft(add(element, current.getLeft()));
39 } else {
40 current.setRight(add(element, current.getRight()));
41 }
42 return current;
43 }
A public method is used to call the private recursive add with initial values
This method is very similar to a binary search
Keep going left/right down the tree based on the ordering of the tree and value of the element being added
As soon as an empty spot is found, insert the node there
The inserted value will be in a leaf node
27.4. Minimum & Maximum
204 @Override
205 public T min() {
206 if (isEmpty()) {
207 throw new NoSuchElementException("Empty Tree");
208 }
209 return min(root);
210 }
211
212 /**
213 * Helper method for enabling a recursive search for the minimum element in the binary search tree.
214 *
215 * @param current Current node being looked at
216 * @return The minimum element in the tree
217 */
218 private T min(Node<T> current) {
219 if (current.getLeft() == null) {
220 return current.getData();
221 } else {
222 return min(current.getLeft());
223 }
224 }
225
226 @Override
227 public T max() {
228 if (isEmpty()) {
229 throw new NoSuchElementException("Empty Tree");
230 }
231 Node<T> current = root;
232 // Iterate right until right most node is found
233 while (current.getRight() != null) {
234 current = current.getRight();
235 }
236 return current.getData();
237 }
Fortunately the minimum and maximum methods are simple
Due to the ordered nature of the binary search tree, go all the way to the left/right for the minimum/maximum value
The minimum functionality is implemented recursively and the maximum is implemented iteratively
This is done for demonstration purposes only
27.5. Remove Minimum & Maximum
Finding the minimum and maximum values in the linked binary search tree is relatively simple
However, removing from the tree adds extra complexity as there is a need to preserve the binary search tree ordering of the tree
27.5.1. Remove Minimum
138 @Override
139 public T removeMin() {
140 T returnElement = null;
141 if (isEmpty()) {
142 throw new NoSuchElementException("Empty Tree");
143 } else if (root.getLeft() == null) {
144 returnElement = root.getData();
145 root = root.getRight();
146 } else {
147 returnElement = removeMin(root, root.getLeft());
148 }
149 size--;
150 return returnElement;
151 }
152
153 /**
154 * Helper method for a recursive removeMin.
155 *
156 * @param parent The parent of the current node
157 * @param current The current node
158 * @return Minimum element in the binary search tree
159 */
160 private T removeMin(Node<T> parent, Node<T> current) {
161 if (current.getLeft() == null) {
162 parent.setLeft(current.getRight());
163 return current.getData();
164 } else {
165 return removeMin(current, current.getLeft());
166 }
167 }
Above are two methods that work together to remove the minimum value
First, take note of the public method
It checks if the tree is empty
It checks if the root happens to be the left most node
If it is, then make the root’s right child the new root of the whole tree
Otherwise, call the private recursive method looking down the left subtree
If the public method doesn’t remove the minimum, the recursive private method is the one that goes looking for the minimum to remove
At this point,
current
exists (is notnull
) since this was checked in the calling methodRemember, if the root’s left child does not exist, then the root must contain the minimum value in the tree
Start with the recursive case — if
current
’s left child does exist, then call the recursive function again to keep looking down the left subtreesIf
current
’s left subtree does not exist, thencurrent
contains the minimum valueSimply replace
parent
’s left child (whichcurrent
also references) withcurrent
’s right childIt does not matter if
current
’s right child isnull
or not, it works either way
27.5.2. Remove Maximum
removeMax
could be implemented with the same recursive idea asremoveMin
However, for demonstration purposes, an iterative method is used instead
171 /**
172 * An iterative implementation of finding the maximum element in a binary search tree. This method could be
173 * recursive like removeMin; however, the iterative one is included for demonstrative purposes.
174 *
175 * @return The maximum element in the binary search tree
176 */
177 @Override
178 public T removeMax() {
179 T returnElement = null;
180 if (isEmpty()) {
181 throw new NoSuchElementException("Empty Tree");
182 }
183 if (root.getRight() == null) {
184 returnElement = root.getData();
185 root = root.getLeft();
186 } else {
187 Node<T> parent = root;
188 Node<T> current = root.getRight();
189 // Iterate right until right most node is found
190 while (current.getRight() != null) {
191 parent = current;
192 current = current.getRight();
193 }
194 returnElement = current.getData();
195 parent.setRight(current.getLeft());
196 }
197 size--;
198 return returnElement;
199 }
removeMax
is similar to the publicremoveMin
methodCheck if the root exists
Check if the root’s right exists
It’s the else case that’s different
The idea is, loop down to the right until there is no more right children
Then, once the right most node is found, set it’s
parent
’s right child to the node being removed’s left childAgain, it doesn’t matter if the left child is
null
or not
27.6. General Remove
This is probably the most complex functionality discussed this course
To help, the discussion will be broken up
47 @Override
48 public boolean remove(T element) {
49 if (!contains(element)) {
50 return false;
51 }
52 int comparison = root.getData().compareTo(element);
53 if (comparison == 0) {
54 root = findReplacementNode(root);
55 } else if (comparison > 0) {
56 remove(element, root, root.getLeft());
57 } else {
58 remove(element, root, root.getRight());
59 }
60 size--;
61 return true;
62 }
The public
remove
method is similar to the publicremoveMin
andremoveMax
It checks the root node to see if it is the value to be removed
Otherwise, it checks which subtree to continue the search down and calls the recursive private
remove
66 /**
67 * Helper method for recursive remove.
68 *
69 * @param element Element to me removed
70 * @param parent The parent of the current node
71 * @param current The current node
72 * @return The element being removed
73 */
74 private T remove(T element, Node<T> parent, Node<T> current) {
75 int comparison = current.getData().compareTo(element);
76 if (comparison == 0) {
77 if (parent.getData().compareTo(current.getData()) > 0) {
78 parent.setLeft(findReplacementNode(current));
79 } else {
80 parent.setRight(findReplacementNode(current));
81 }
82 return current.getData();
83 } else if (comparison > 0) {
84 return remove(element, current, current.getLeft());
85 } else {
86 return remove(element, current, current.getRight());
87 }
88 }
The private
remove
is basically doing a binary search through the tree looking for the value to be removedUnlike the binary search however, this method must
Remove the element
Potentially address a gap in the tree if the node being removed is an internal node
Ensure the binary search tree ordering is preserved
To do this, another private method called
findReplacementNode
is used
93 /**
94 * Helper method for finding which node should be used to replace a node being removed.
95 * There are a few conditions:
96 * 1. Both left and right children are null. Here, simply remove the node.
97 * 2. Left child is not null but right child is null. Replace the toRemove node with the left child.
98 * 3. Left child is null but right child is not null. Replace the toRemove node with the right child.
99 * 4. Both left and right children are not null. Replace the toRemove node with the in order successor, which would
100 * be the right subtree's left most node.
101 *
102 * @param toRemove The node being replaced
103 * @return The node replacing the node being removed
104 */
105 private Node<T> findReplacementNode(Node<T> toRemove) {
106 Node<T> replacementNode = null;
107 if (toRemove.getLeft() == null && toRemove.getRight() == null) {
108 replacementNode = null;
109 } else if (toRemove.getLeft() != null && toRemove.getRight() == null) {
110 replacementNode = toRemove.getLeft();
111 } else if (toRemove.getLeft() == null && toRemove.getRight() != null) {
112 replacementNode = toRemove.getRight();
113 } else {
114 Node<T> parent = toRemove;
115 Node<T> current = toRemove.getRight();
116 // Find the in order successor --- toRemove's right child's left most node (minimum node)
117 while (current.getLeft() != null) {
118 parent = current;
119 current = current.getLeft();
120 }
121 replacementNode = current;
122 // Set replacementNode's left to toRemove's left
123 replacementNode.setLeft(toRemove.getLeft());
124 // Update replacementNode's right if necessary --- if the in order successor of toRemove is its immediate
125 // right, no update is needed. Otherwise, the replacementNode's parent's left is set to the
126 // replacementNode's right and the replacementNode's right is set to toRemove's right.
127 if (toRemove.getRight() != replacementNode) {
128 parent.setLeft(replacementNode.getRight());
129 replacementNode.setRight(toRemove.getRight());
130 }
131 }
132 return replacementNode;
133 }
findReplacementNode
looks rather intimidating at first, but if studied carefully, it is actually rather simpleThe first part, the first
if
s andelse if
s check if the node being removed is a leaf node, or if it has one childIf it’s a leaf node, there is no replacement
If there is only one child, then that child becomes the replacement
The other case is when there are two children
Here, the idea is to find the in order successor node
The right child’s left most node
This code is very similar to the private
removeMax
methodIterate down the tree until the left most node in the subtree is found
This node will become the replacement node
The replacement node’s left subtree will be the removed node’s old left subtree
The replacement node contains the smallest thing in the whole right subtree
But since it is in the right subtree, it is bigger than everything in the removed node’s left subtree
If it happens that the node being removed’s right child is the replacement node, then done
If it is not the right child, then
The replacement node’s parent may need to take care of the replacement node’s right subtree
The node being removed may have a right subtree, that when all is said and done, still needs to go somewhere
The idea is, since the parent to the replacement node must be larger than everything in its left subtree, the replacement node’s right subtree is also less than the parent
Therefore, make the parent node’s new left subtree the replacement node’s right subtree
Since the replacement node is the smallest thing in the right subtree, the replacement node’s right subtree can be made to be the removed node’s right subtree
27.7. contains
All data structures implemented have a way to check if a given element is contained within it
The binary search tree is no different, but here a linear search will not be used
Instead, make use of a binary search to help find the element within the data structure
241 @Override
242 public boolean contains(T element) {
243 return binarySearch(element, root) != null;
244 }
245
246 /**
247 * Helper method enabling a recursive binary search for a given element.
248 *
249 * @param element Element being searched for
250 * @param current Current node being investigated
251 * @return Node containing the element searched for, or null if not found
252 */
253 private Node<T> binarySearch(T element, Node<T> current) {
254 if (current == null) {
255 return null;
256 }
257 int comparison = current.getData().compareTo(element);
258 if (comparison == 0) {
259 return current;
260 } else {
261 if (comparison > 0) {
262 return binarySearch(element, current.getLeft());
263 } else {
264 return binarySearch(element, current.getRight());
265 }
266 }
267 }
See the above method called
contains
and the recursivebinarySearch
methodWhat’s interesting here is
contains
needs to return a boolean, but thebinarySearch
returns a reference to a nodeA way to address this is to simply check if
binarySearch
returned a reference to a node or notIf
contains
gets a node back, then returntrue
Otherwise, if
null
, returnfalse
As this method is written, duplicate values are assumed to be in the right subtree
27.8. count
271 @Override
272 public int count(T element) {
273 if (isEmpty()) {
274 return 0;
275 }
276 return count(element, root);
277 }
278
279 /**
280 * Helper method for recursive count of elements in binary search tree.
281 *
282 * @param element Element to be counted
283 * @param current Current node being investigated
284 * @return Number of times the element exists in the (sub)tree
285 */
286 private int count(T element, Node<T> current) {
287 if (current == null) {
288 return 0;
289 }
290 int comparison = current.getData().compareTo(element);
291 if (comparison == 0) {
292 // With this implementation, equal elements are always to the right
293 return 1 + count(element, current.getRight());
294 } else if (comparison > 0) {
295 return count(element, current.getLeft());
296 } else {
297 return count(element, current.getRight());
298 }
299 }
Counting the number of times a given element exists within the tree will be similar to a binary search
The key difference will be, do a binary search, but if the element is found, continue looking in the subtree where duplicates are added
Notice the line
return 1 + count(element, current.getRight())
This continues the search to the right
Whatever the result of the search down the right subtree returns, add one to it before returning
27.9. For Next Time
Read Chapter 11 Sections 1 – 3
17 pages
27.9.1. Playing Code
Download and play with