Using a array list in which we maintain the elements in sorted order allows us to use a binary search for our lookup, which is logarithmic (O(log n)); adding elements, however, to such a sorted vector would involve a linear (O(n)) operation (the new element would have to be inserted at it proper position to maintain sortedness and the rest of the vector to its right slid over one position. The insertion of the n elements (assuming they arrived in random order) would thus be of quadratic cost.
What is needed are lookup AND insertion operations that are both O(log n) or better. There are two basic structures that provide us with such performance: trees (logarithmic), and hash tables (constant if done right).
class Node<E> { Node(Node<E>, E data, Node<E> right) { this.data = data; this.left = left; this.right = right; } private E data; private Node≤E> left, right; }
class BinaryTree { … Node<E> root; }
addRoot(data)
- creates a single node (root-only) binary tree
void addRoot(E data) {root = new Node(data);}
addLeft(data, node)
- adds data as left child of node (only if no left child already)
void addLeft(E data, Nodenode) { if (node == null) throw new CollectionException("tree", "addLeft", "Non-existent node"); if (node.left != null) throw new CollectionException("binary tree", "addLeft", "node already has left child"); var newNode = new Node (data); node.left = newNode; }
addRight(data, node)
- adds data as right child of node (only if no right child already)
void addRight(E data, Nodenode) { if (node == null) throw new CollectionException("tree", "addRight", "Non-existent node"); if (node.right != null) throw new CollectionException("binary tree", "addRight", "node already has right child"); var newNode = new Node (data); node.right = newNode; }
void prePrint(Node<E> root) { if (root == null) return; System.out.println(root.data) prePrint(root.left) prePrint(root.right) }
void inPrint(Node<E> root) { if (root == null) return; inPrint(root.left) System.out.println(root.dataa) inPrint(root.right) }
void postPrint(Node<E> root) { if (root == null) return; postPrint(root.left) postPrint(root.right) System.out.println(root.data) }
Node Count
#nodes in a tree =
int nodeCount(Node<E> root) { if (root == null) return 0; return nodeCount(root.left) + nodeCount(root.right) + 1 }
Leaf Count
#leaves in a tree =
int leafCount(Node<E> root) { if (root == null) return 0; if (root.left == null && root.right == null) return 1; return leafCount(root.left) + leafCount(root.right); }
Depth of a Tree
int depth(Node<E> root) { if (root == null) return 0 return max(depth(root.left), depth(root.right)) + 1 }
Finding a Value in the Tree
value is there if
boolean contains(Node<E> root, E data) { if (root == null) return false if (data == root.data) return true return contains(root.left, data) || contains(root.right, data) }
||
Finding the Maximum Value in the Tree
Maximum value of a (sub)tree is the maximum of the root's value and the maximum values of the left and right subtrees
int max(Node<E> root) { if (root == null) return 0 // a 'cheat' return Math.max(root.data, max(root.left), max(root.right)) }
int max(Node<E> root) { // a delegator of sorts if (root == null) throw new Exception("can't take max of empty tree"); return max2(root); } int max2(Node<E> root) { // private int result = root.data; if (root.left != null) result = max(result, max2(root.left); if (root.right != null) result = max(result, max2(root.right); return result; }
class GenTreeNode<E> { E data; ArrayList<GenTreeNode<E>> children; }
log
(as opposed to n
cost for searching)
value is there if
bool contains(Node<E> root, E data) const { if (root == null) return false; if (data == root.data) return true; if (data.compareTo(root.data) < 0) return contains(root.left, data); return contains(root.right, data); }
class BinarySearchTree { … private: BinaryTree binaryTree; };
addLeft
) to maintain the 'ordering' structure
BinarySearchTree
class.
void add(E data) { Node<E> p = root, q = null; while (p != null) { q = p; if (p.data.equals(data)) return; else if (p.data.compareTo(data) < 0) p = p.left; else p = p.right; } if (q == null) binaryTreetree.addRoot(data); else if (q.data.compareTo(data) < 0) binaryTree.addLeft(q, data); else binaryTree.addRight(q, data); }or
// Recursively void add(E data) { if (binaryTree.root == null) binaryTree.addRoot(data); else add(binaryTree.root, data); } private void add(Nodeorroot, E data) { if (data.equals(root.data)) return; if (data.compareTo(root.data) < 0) { if (root.left == null) binaryTree.addLeft(data, root); else add(root.left, data); } else { if (root.right == null) binaryTree.addRight(data, root); else add(root.right, data); } }
// Directly manipulating the underlying nodes (i.e., no composition)
void add(E data) {
Node<E> p = root, q = 0;
while (p != null) {
q = p;
if (p.data.equals(data))
return;
else if (p.data < data)
p = p.left;
else
p = p.right;
if (q == null)
root = new Node<E>(data);
if (q.data.compareTo(data) < 0)
insertLeft(q, data);
else
insertRight(q, data);
}