Tree: Set of nodes connected by edges that indicate the relationships among the nodes.
Two Forms:
Traversal: Process of processing each node exactly once.
In general, to iterate through a tree, we need to use a stack or recursion.
Binary Tree: Each node can have between 0—2 nodes.
Types of Binary Trees:
Height: \boxed{ \text{Height of a Full Binary Tree} = 2^\text{Number of Nodes} - 1 }
Importance: Many things can be represented with binary trees, which can make operations O \log (n)
Steps:
Ways to Traverse a Binary Tree:
Note: Given preorder and inorder traversal, or postorder and inorder traversal, you can reconstruct the tree.
Note: There is no inorder traversl because there’s no concept of left and right.
Ways to Traverse a General Tree:
Note: Data Structure for Traversal
- Preorder: Stack
- Level-Order: Queue
This interface contains basic methods for both tree types:
public interface TreeInterface<T> {
getRootData()
T int getHeight();
int getNumberOfNode();
boolean isEmpty();
void clear();
}
This interface contains all four iterators:
import java.util.Iterator;
public interface TreeIteratorInterface<T> {
Iterator<T> getPreorder();
Iterator<T> getPostorder();
Iterator<T> getInorder();
Iterator<T> getLevelOrder();
}
public interface BinaryTreeInteface<T> extends TreeInterface<T>, TreeIteratorInterface<T> {
void setRootData(T rootData);
void setTree(T rootData, BinaryTreeInterface<T> leftTree, BinaryTreeInterface<T> rightTree);
}
Example: Manually building a tree
// Build leaves <String> bTree = new BinaryTree<>(); BinaryTreeInterface.setTree("B", null, null); bTree <String> cTree = new BinaryTree<>(); BinaryTreeInterface.setTree("C", null, null); cTree <String> emptyTree = new BinaryTree<>(); BinaryTreeInterface // Form larger subtree (root B and C to A) <String> aTree = new BinaryTree<>(); BinaryTreeInterface.setTree("A", bTree, cTree); aTree
- Of course, this is just an example.
- Note: If we have a non-leave node that’s missing a tree, make sure the side is set to an empty tree. Only the leave nodes should have null children.
- Empty trees are like sentinel values.
Expression Tree: Binary tree representation of an expression.
Important: Things to Know when Recreating Expression Trees from their Traversals
- First node in preorder traversal is the root node.
- Last node in postorder traversal is the root node.
- Inorder traversal only tells us which operands are left and right of operations.
Example: Evaluating an expression tree
Algorithm evaluate(expressionTree) { if expressionTree is a leaf node { # (l and r children == null) return the value of the leaf node; } else { firstOperand = evaluate(left subtree) secondOperand = evaluate(right subtree) operator = root of expression tree return firstOperand operator secondOperator } }
Real-World Example: Expert systems with decision trees
- Tech support troubleshooting decision trees.
- Guessing games.
Example: Interface for a decision tree.
public interface DecisionTreeInterfacemT< extends BinaryTreeInterface<T> { getCurrentData(); T void setCurrentData(T newData); void setResponses(T responseForNo, T responseForYes); boolean isAnswer(); void advanceToNo(); void advanceToYes(); void resetCurrentNode(); }
Note the hierarchy of inheritance:
- Tree
- Binary Tree
- Decision Tree
Binary Search Tree: A binary tree where each node’s value is:
Cool Characteristic: In-Order Traversal of BST
- In-order traversal of a binary search tree gives us the contents of the tree in sorted order (low to high)
- (Pre and post-order traversal doesn’t give us anything intersting)
On Performance: \boxed{ \text{(Balanced) BST Search Performance: } O ( \log n ) } \\ \small\textit{(where $n$ is the height of the tree)}
Example: How-To Search a BST
Algorithm bstSearch(tree, needle) { if tree is empty return false else if needle == root return true else if needle < root return bstSearch(tree's left subtree, needle) else needle > root return bstSearch(tree's right subtree, needle) }
- Be cognizant of stack overflow when using recursion.
Heap: A complete binary tree where each node is (1) smaller or (2) larger than all objects in its descendants.
Two Types of Heaps:
Example: Maxheap Interface
public interface MaxHeapInterface<T extends Comparable<? super T>> { void add (T newEntry); removeMax(); T getMax(); T boolean isEmpty(); int getSize(); void clear(); }
T getMax()
is really justT getRootData()
from theTreeInterface
Implementation Note: We usually implement heaps with arrays.
Cool Characteristic: Behaves like a priority queue
- Because the that order things go in relates to how things come out.
- Like a mix of array, priority queue, and tree.
Parse Tree:
Real-World Examples of Parse Trees:
- Used by compilers, syntax checkers, etc.
- Creating a parse tree for a game of tic-tac-toe
Relationship between Binary Tree and General Tree:
- You can implement a general tree with a binary tree.
- e.g., Let all right-nodes be siblings, let all left node be children.
Each node has three fields:
class BinaryNode<T> {
private T data;
private BinaryNode<T> leftChild;
private BinaryNode<T> rightChild;
public BinaryNode() {
this(null);
}
public BinaryNode(T dataPortion) {
this(dataPortion, null, null);
}
public BinaryNode(T dataPortion, BinaryNode<T> leftChild, BinaryNode<T> rightChild) {
this.data = dataPortion;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
private void initializeTree(T rootData, BinaryTree<T> leftTree, BinaryTree<T> rightTree)
private
and public
version of a method.Recursive:
private void inorderTraverse(BinaryNode<T> node) {
if (node != null) {
inorderTaverse(node.getLeftChild());
System.out.println(node.getData());
inorderTaverse(node.getRightChild());
}
}
Stack:
Use a queue.
public interface ExpressionTreeInterface extends BinaryTreeInterface<String> {
double evaluate();
}
Steps: For each token:
Example: Manually converting postfix to expression tree
Prefix:
ab+
We will use a binary tree stack, a stack of binary trees.
- We will read the first character onto the stack as a leaf node (a)
- We will add the second character onto the stack as a leaf node (b)
- Once we read an operator, we will pop off the two leaf nodes and make them children of the operator (+)
private double evaluate(BinaryNode<String> rootNode) {
double result;
if (rootNode == null) {
return 0;
}
if (rootNode.isLeaf()) {
String variable = rootNOdegetData();
= getValueOf(Variable);
result } else {
double firstOperand = evaluate(rootNode.getLeftChild());
double secondOperand = evaluate(rootNode.getRightChild());
/* Perform computation */
}
}
You can implement a general tree with (1) lists, or (2) a binary tree!
Representing General Tree with a Binary Tree:
Tip: Draw the binary with horizontal right-lines to make the sibling relationship clearer.