What if we want something that’s reasonably fast at both searching and inserting?
We will start by taking a look at binary search trees and will look at different kinds of balanced trees and set/dictionary implementations later
A Binary Search Tree (BST) is a linked structure where:
Each node has at most two children
everything in a left subtree is smaller than its parent node
everything in a right subtree is larger than its parent node
We will look at 6 functions (5 public and 1 private)
Data in Node that we want to add: 7
What do we have to do to add Node 7 to the right spot?
BSTs are only efficient when they’re balanced.
In the worst case you get O(n) operations – how?
You will learn about self-balancing trees like AVL trees and Red-Black trees (later)
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
In the worst case you get O(n) operations – how?
You have 10 minutes to complete the quiz.
Whether you check one or more options is up to you.
As a reminder, here’s what we had for our generic Node class:
public class Node<T> {
private T data;
private Node<T> left;
private Node<T> right;
// constructor with data as argument
public Node(T data) {
this.data = data;
left = null;
right = null;
}
// getters
public T getData() {
return data;
}
public Node<T> getLeft() {
return left;
}
public Node<T> getRight() {
return right;
}
// setters
public void setData(T data) {
this.data = data;
}
public void setLeft(Node<T> node) {
left = node;
}
public void setRight(Node<T> node) {
right = node;
}
}We need to implement extend Comparable to be able to go left or right (use compareTo from the data object)
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> left;
private Node<T> right;
public Node(T data) {
this.data = data;
left = null;
right = null;
}
public T getData() {
return data;
}
public Node<T> getLeft() {
return left;
}
public Node<T> getRight() {
return right;
}
public void setData(T data) {
this.data = data;
}
public void setLeft(Node<T> node) {
left = node;
}
public void setRight(Node<T> node) {
right = node;
}
public String toString() { return data.toString(); }
}This cursive method should be private.
private void addNode(Node<T> current, Node<T> nodeToAdd)
Strategy:
compareTo)addNode recursively with left/right child as current nodeprivate void addNode(Node<T> current, Node<T> nodeToAdd) {
if (current.getData().compareTo(nodeToAdd.getData()) > 0) {
if (current.getLeft() == null) current.setLeft(nodeToAdd);
else addNode(current.getLeft(), nodeToAdd);
} else if(current.getData().compareTo(nodeToAdd.getData()) < 0) {
if (current.getRight() == null) current.setRight(nodeToAdd);
else addNode(current.getRight(), nodeToAdd);
}
}
public void addNode(Node<T> node) {
if (root == null) root = node;
else addNode(root, node);
}It’s going to be easy to test adding multiple nodes if we implement toString
Here’s the public toString:
Implement a private toString(Node)
These traverse as far down a branch as possible before backtracking:
Inorder (Left → Root → Right)
Preorder (Root → Left → Right)
These traverse as far down a branch as possible before backtracking:
Postorder (Left → Right → Root)
Level-order
One way to implement this that I find useful for debugging:
private String toString(Node<T> current) {
if (current == null) {
return "";
}
String message = "";
if (current.getLeft() != null) {
message += current.getData() + " left child is " + current.getLeft().getData() + "\n";
}
if (current.getRight() != null) {
message += current.getData() + " right child is " + current.getRight().getData() + "\n";
}
if (current.getLeft() != null) {
message += toString(current.getLeft());
}
if (current.getRight() != null) {
message += toString(current.getRight());
}
return message;
}Before you start writing code, what is the strategy here?
Before you start writing code, what is the strategy here?
What’s the runtime?
public Node<T> search(T value) {
return search(root, value);
}
private Node<T> search(Node<T> current, T value) {
if (current == null) return null;
if (current.getData().compareTo(value) == 0) return current;
if (current.getData().compareTo(value) > 0) {
if (current.getLeft() == null) return null;
return search(current.getLeft(), value);
}
if (current.getRight() == null) return null;
return search(current.getRight(), value);
}Best/Average case: \(O(\log n)\) when the tree is balanced
Worst case: \(O(n)\) (why?)
Before you start writing code, what is the strategy here?
General idea - search for node to remove and its parent.
Leaf
One child (two cases really, left/right)
Two children
A leaf can be removed without breaking the tree
Node with one child can be removed with only locally available pointers
Node with one child can be removed with only locally available pointers
Node with two children
Node with two children – replace node to remove data with min to the right data (or max to the left data)
Node with two children – replace node to remove data with min to the right data (or max to the left data)
find parent of node to remove
modify parent.left/right to not reference node to remove
count how many children node to remove has
public void remove(T value) {
// find node to remove and its parent
Node<T> parent = null;
Node<T> current = root;
while (current != null && current.getData().compareTo(value) != 0) {
parent = current;
if (current.getData().compareTo(value) > 0) {
current = current.getLeft();
} else {
current = current.getRight();
}
}
if (current != null) {
remove(parent, current);
}
}
private void remove(Node<T> parent, Node<T> nodeToRemove) {
// count number of children
int children = 0;
Node<T> child = null;
if (nodeToRemove.getLeft() != null) {
children++;
child = nodeToRemove.getLeft();
}
if (nodeToRemove.getRight() != null) {
children++;
child = nodeToRemove.getRight();;
}
// address each case
if (children <= 1) {
if (parent == null) root = child;
else if (parent.getLeft() == nodeToRemove) parent.setLeft(child);
else parent.setRight(child);
}
if (children == 2) {
// find minimum to the right
// or find max to the left
Node<T> previous = nodeToRemove;
Node<T> replacement = previous.getRight();
while (replacement.getLeft() != null) {
previous = replacement;
replacement = replacement.getLeft();
}
nodeToRemove.setData(replacement.getData());
if (previous == nodeToRemove) previous.setRight(null);
else previous.setLeft(null);
}
}public class Tree<T extends Comparable<T>> {
private Node<T> root;
public Tree() { root = null; }
private void addNode(Node<T> current, Node<T> nodeToAdd) {
if (current.getData().compareTo(nodeToAdd.getData()) > 0) {
if (current.getLeft() == null) {
current.setLeft(nodeToAdd);
} else {
addNode(current.getLeft(), nodeToAdd);
}
} else if(current.getData().compareTo(nodeToAdd.getData()) < 0) {
if (current.getRight() == null) {
current.setRight(nodeToAdd);
} else {
addNode(current.getRight(), nodeToAdd);
}
}
}
public Node<T> getRoot() { return root; }
public void addNode(Node<T> node) {
if (root == null) root = node;
else addNode(root, node);
}
private String toString(int level, Node<T> current) {
if (current == null) return "";
String retVal = toString(level + 1, current.getLeft());
for (int i = 0; i < level; i++) retVal += "\t";
retVal += current.getData() + "<";
retVal += "\n";
retVal += toString(level + 1, current.getRight());
return retVal;
}
private String toString(Node<T> current) {
if (current == null) return "";
String message = "";
if (current.getLeft() != null) {
message += current.getData() + " left child is " + current.getLeft().getData() + "\n";
}
if (current.getRight() != null) {
message += current.getData() + " right child is " + current.getRight().getData() + "\n";
}
if (current.getLeft() != null) {
message += toString(current.getLeft());
}
if (current.getRight() != null) {
message += toString(current.getRight());
}
return message;
}
public String toString() {
return toString(0, root);
//return root.getData() + " is the root\n" + toString(root).trim();
}
public Node<T> findMin() {
return findMin(root);
}
private Node<T> findMin(Node<T> current) {
if (current == null) return current;
if (current.getLeft() == null) return current;
return findMin(current.getLeft());
}
public Node<T> findMax() {
return findMax(root);
}
private Node<T> findMax(Node<T> current) {
if (current == null) return current;
if (current.getRight() == null) return current;
return findMax(current.getRight());
}
public Node<T> search(T value) {
return search(root, value);
}
private Node<T> search(Node<T> current, T value) {
if (current == null) return null;
if (current.getData().compareTo(value) == 0) return current;
if (current.getData().compareTo(value) > 0) {
if (current.getLeft() == null) return null;
return search(current.getLeft(), value);
}
if (current.getRight() == null) return null;
return search(current.getRight(), value);
}
private void remove(Node<T> parent, Node<T> nodeToRemove) {
// count number of children
int children = 0;
Node<T> child = null;
if (nodeToRemove.getLeft() != null) {
children++;
child = nodeToRemove.getLeft();
}
if (nodeToRemove.getRight() != null) {
children++;
child = nodeToRemove.getRight();;
}
// address each case
if (children <= 1) {
if (parent == null) root = child;
else if (parent.getLeft() == nodeToRemove) parent.setLeft(child);
else parent.setRight(child);
}
if (children == 2) {
// find minimum to the right
// or find max to the left
Node<T> previous = nodeToRemove;
Node<T> replacement = previous.getRight();
while (replacement.getLeft() != null) {
previous = replacement;
replacement = replacement.getLeft();
}
nodeToRemove.setData(replacement.getData());
if (previous == nodeToRemove) previous.setRight(null);
else previous.setLeft(null);
}
}
public void remove(T value) {
// find node to remove and its parent
Node<T> parent = null;
Node<T> current = root;
while (current != null && current.getData().compareTo(value) != 0) {
parent = current;
if (current.getData().compareTo(value) > 0) {
current = current.getLeft();
} else {
current = current.getRight();
}
}
if (current != null) {
remove(parent, current);
}
}
}Tree height \(h\) is \(\log n\) for balanced tree.
Search, Insert, Delete:
Find Minimum/Maximum:
Traversal (inorder, preorder, postorder):