-
Notifications
You must be signed in to change notification settings - Fork 0
/
HashedBinarySearchTree.java
453 lines (387 loc) · 10.2 KB
/
HashedBinarySearchTree.java
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
package ch.hslu.AD.SW03.BinaryTree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
public class HashedBinarySearchTree<T extends Comparable<T>> implements Tree<T> {
/**
* Maximum allowed children for each node.
*/
private static int TREE_ORDER = 2;
private Node root;
/**
* Add the given element to the tree.
*
* @param element the element to add to the tree.
*
* @return if the element could be added.
*/
public boolean add(T element) {
if(element == null) {
throw new NullPointerException("Failed to create Node with 'null' element");
}
if(root == null) {
// we insert the first element as root node
root = new Node(element, null);
return true;
}
return insert(root, element, element.hashCode());
}
/**
* Insert the given element in the correct order into the given root node.
*
* The tree is traversed in a pre-order fashion.
*
* @param root the root node of the (sub)tree
* @param element the element to insert
* @return
*/
private boolean insert(Node root, T element, int hash) {
if(root == null) {
// we are forced to abort because we cannot insert here
// because we don't know the root's predecessor.
return false;
}
if(root.getElement().equals(element)) {
// we already have this value in the tree.
return true;
}
if(hash < root.getHash()) {
if(root.left == null) {
// insert node here because we don't have this branch yet.
root.left = new Node(element, root);
return true;
}
return insert(root.left, element, hash);
}
if(hash > root.getHash()) {
if(root.right == null) {
// insert node here because we don't have this branch yet.
root.right = new Node(element, root);
}
return insert(root.right, element, hash);
}
// This can happened if the equals/compareTo contract is not respected
// by type T. In case equals() returns false and compareTo() returns 0.
// Someone screwed up, but ... "wasn't me"
return false;
}
/**
* Remove the given element from the tree
*
* @param element the element to remove from the tree
*
* @throws NoSuchElementException
*/
public boolean remove(T element) {
try {
root = remove(root, element, element.hashCode());
} catch (NoSuchElementException e) {
return false;
}
return true;
}
/**
* Remove the given element from the given root node.
*
* The tree is traversed in a post-order fashion.
*
* @param root the root node of the (sub)tree.
* @param element the element to remove.
* @return the Node which is the new root node.
*
* @throws NoSuchElementException
*/
private Node remove(Node root, T element, int hash) {
if(root == null) {
// we probably have an empty tree or messed up insertion
throw new NoSuchElementException();
}
if(hash < root.getHash()) {
if(root.left == null) {
// "You know nothing, John Snow"
throw new NoSuchElementException();
}
root.left = remove(root.left, element, hash);
} else if(hash > root.getHash()) {
if(root.right == null) {
// "You know nothing, John Snow"
throw new NoSuchElementException();
}
root.right = remove(root.right, element, hash);
} else {
if(!root.getElement().equals(element)) {
throw new NoSuchElementException(); // someone screwed up the T compareTo/equals contract ...
}
if(root.isLeaf()) {
// in case the node does not have any child / is a leaf.
root = null;
} else if(root.right == null) {
// in case the node only has a left child
// fix up parent node
root.left.parent = root.parent;
root = root.left;
} else if(root.left == null) {
// in case the node only has a right child
// fix up parent node
root.right.parent = root.parent;
root = root.right;
} else {
// in case the node has two children
// get the replacement node
Node replacement = getLeftMost(root.right);
// TODO(TF): remove replacement node
replacement.parent = root.parent;
replacement.left = root.left;
replacement.right = remove(root.right, replacement.getElement(), replacement.getHash());
root = replacement;
}
}
return root;
}
/**
* Get the left and bottom most node of a given (sub)tree.
*
* This method is used to remove a node with two children.
*
* @param root the root element of the (sub)tree.
* @return the left and bottom most node.
*/
private Node getLeftMost(Node root) {
if(root.left == null) {
return root;
}
return getLeftMost(root.left);
}
/**
* Return whether the given element is a node in the tree.
*
* @param element the element to search for
* @return if the element is a node of the tree.
*/
public boolean contains(T element) {
return contains(root, element, element.hashCode()) != null;
}
/**
* Return whether the given (sub)tree contains the given element.
*
* The tree is traversed in a pre-order fashion.
*
* @param root the root node of the (sub)tree.
* @param element the element to search for.
* @return the found node.
*/
private Node contains(Node root, T element, int hash) {
if(root == null) {
// we probably have an empty tree or messed up insertion
return null;
}
if(root.getElement().equals(element)) {
// we've found the searched element
return root;
}
if(hash < root.getHash()) {
if(root.left == null) {
// "You know nothing, John Snow"
return null;
}
return contains(root.left, element, hash);
}
if(hash > root.getHash()) {
if(root.right == null) {
// "You know nothing, John Snow"
return null;
}
return contains(root.right, element, hash);
}
// FIXME(TF): we messed the the insertion up
return null;
}
/**
* Balance the tree.
*/
public void balance() {
if(root == null) {
return; // empty tree, thus we do nothing.
}
List<T> elements = root.toList();
// sort elements
Collections.sort(elements);
root = balance(elements, null);
}
private Node balance(List<T> elements, Node parent) {
if(elements.isEmpty()) {
return null; // we are at the end
}
// get middle node
int middleIndex = elements.size() / 2;
Node node = new Node(elements.get(middleIndex), parent);
node.left = balance(elements.subList(0, middleIndex), node);
node.right = balance(elements.subList(middleIndex + 1, elements.size()), node);
return node;
}
public List<T> toList() {
if(root == null) {
return new ArrayList<T>();
}
return root.toList();
}
/**
* Return the maximum order of the tree.
*
* Because this is a binary search tree the order
* is always 2.
*/
public int getOrder() {
return TREE_ORDER;
};
/**
* Get the degree of the given element in this tree.
*/
public int getDegree(T element) {
Node node = contains(root, element, element.hashCode());
if(node == null) {
throw new NoSuchElementException();
}
return node.getDegree();
}
/**
* Get the path to the given element in this tree.
*/
public List<T> getPath(T element) {
Node node = contains(root, element, element.hashCode());
if(node == null) {
throw new NoSuchElementException();
}
return node.getPath();
}
/**
* Get the depth of the given element in this tree.
*/
public int getDepth(T element) {
Node node = contains(root, element, element.hashCode());
if(node == null) {
throw new NoSuchElementException();
}
return node.getDepth();
}
/**
* Get the height of this tree.
*/
public int getHeight() {
if(root == null) {
return 0;
}
return root.getHeight();
}
/**
* Get the height of the given element in this tree.
*
* @param element the element for the height.
* @return the height of the given element.
*/
public int getHeight(T element) {
Node node = contains(root, element, element.hashCode());
if(node == null) {
throw new NoSuchElementException();
}
return node.getHeight();
}
/**
* Get the weight of this tree.
*/
public int getWeight() {
if(root == null) {
return 0;
}
return root.getWeight();
}
/**
* Get the weight of the given element in this tree.
* @param element the weight of the given element.
* @return
*/
public int getWeight(T element) {
Node node = contains(root, element, element.hashCode());
if(node == null) {
throw new NoSuchElementException();
}
return node.getWeight();
}
private class Node {
public Node parent;
public Node left;
public Node right;
private T element;
private int hash;
public Node(T element, Node parent) {
if(element == null) {
throw new NullPointerException("Failed to create Node with 'null' element");
}
this.parent = parent;
this.element = element;
this.hash = element.hashCode();
}
public T getElement() {
return element;
}
public int getHash() {
return hash;
}
public boolean isLeaf() {
return left == null && right == null;
}
public int getDegree() {
if(isLeaf()) {
return 0;
}
if(left != null && right != null) {
return 2;
}
return 1;
}
public List<T> getPath() {
List<T> path = new ArrayList<>();
Node current = this;
while(current != null) {
path.add(0, current.getElement());
current = current.parent;
}
return path;
}
public int getDepth() {
int depth = 0;
Node current = this;
while(current != null) {
depth++;
current = current.parent;
}
return depth;
}
public int getHeight() {
int leftHeight = (left != null) ? left.getHeight() : 0;
int rightHeight = (right != null) ? right.getHeight() : 0;
return Math.max(leftHeight, rightHeight) + 1; // we add one because a leaf has height 1.
}
public int getWeight() {
int leftWeight = (left != null) ? left.getWeight() : 0;
int rightWeight = (right != null) ? right.getWeight() : 0;
return leftWeight + rightWeight + 1; // we add one because a leaf has weight 1.
}
/**
* Return a list of all elements in this tree in pre-order.
* @return
*/
public List<T> toList() {
List<T> list = new ArrayList<>();
list.add(element);
if(left != null) {
list.addAll(left.toList());
}
if(right != null) {
list.addAll(right.toList());
}
return list;
}
}
}