Tree Algorithms
Trees
- Definition: In graph theory, a tree is an undirected graph with exactly one path between every pair of nodes.
- Data Structure Implementation: A tree data structure implements a directed acyclic graph where the single source is the root, and the sinks are the leaves.
- Recursive Definition: A tree is a node containing a payload and a list of trees.
Types of Trees
- Arity of trees:
- Unary tree: linked list
- Binary trees: each node has at most two children
- Ternary trees: each node has at most three children
- N-ary trees: each node has at most n children
- General trees: each node has arbitrarily many children
- Full trees:
- In full n-ary trees, each node has exactly n or 0 children
Tree Traversal
- Depth-first search (DFS):
- Pre-order traversal: perform action before all children
- In-order traversal: perform action between children
- Post-order traversal: perform action after children
- Breadth-first search (BFS):
- Traverse the tree by level
- Perform action on all nodes on a level before starting next level
Reminder: Binary Trees
- Recursive data type
- Recursive definition: a tree is a node with two smaller (sub)trees
- Node in a binary tree (TreeNode)
- TreeNode consists of payload and references to two TreeNodes
TreeNode Class
Example of a simple TreeNode class:
class TreeNode { int payload; TreeNode left; TreeNode right; }
Integer Tree Example
- Illustrates how to create a binary tree with integer payloads in Java.
- The
TreeTestclass demonstrates the instantiation and construction of a tree with integer values. - The
TreeNodeclass includes constructors to initialize nodes with a value, and references to left and right children.
Generic Tree Example
- Demonstrates a generic version of the TreeNode class, allowing it to hold any type of payload.
- The class definition
class TreeNode<T>uses generics to define the payload type.
Max Value in a Binary Tree
- Base case
*Maximum of a leaf node is its own payload - Recursive Call
*Compute maximum for each child
*Return maximum of own payload and children
Implementing Tree Algorithms
- As Instance Methods: Within the node class, suitable for recursive algorithms operating locally on an existing tree.
- In Static Methods: Within the node class, useful for recursive or imperative algorithms operating on an external tree.
- In Methods Outside of Node Class: Best for global algorithms that operate on trees arbitrarily, promoting better encapsulation and separation of concerns.
Printing a Tree
- Recursive Algorithm (Preorder):
- Print the payload of the current node.
- Print the left (smaller) subtree.
- Print the right (smaller) subtree.
Inside the Tree
- Direct Access to Fields:
- The
TreeNodeclass provides direct access to its fields (payload, left, right) for internal operations.
- The
Inside the Tree (Static)
- Direct Access and Safe Null Reference Handling:
- The static method
printPreorderdemonstrates how to safely handle null references when traversing the tree.
- The static method
Outside the Tree
- Encapsulation and Separation of Concerns:
- The
TreeAlgorithmsclass provides methods to operate onTreeNodeobjects without direct access to their internal fields. - Getter methods (
getPayload,getLeft,getRight) are used to access the node's data and children.
- The
Depth-First Search (DFS)
Pre-order, in-order, and post-order traversals are all types of DFS.
DFS has a straightforward recursive implementation.
Pseudocode:
procedure DFS(Node n): performPreorderAction for (Node c : n.children): DFS(c) if (c not last child): performInOrderAction performPostOrderAction
Iterative DFS
Requires maintaining a stack.
In-order and post-order require an additional cursor.
Java Code:
public static void iterativePreorder(TreeNode root) { Deque<TreeNode> stack = new ArrayDeque<TreeNode>(); stack.push(root); while (!stack.isEmpty()) { TreeNode cur = stack.pop(); System.out.println(cur.getPayload()); if (cur.getRight() != null) stack.push(cur.getRight()); if (cur.getLeft() != null) stack.push(cur.getLeft()); } }
Breadth-First Search (BFS)
- BFS traverses the tree level by level.
Iterative BFS
BFS does not have a natural recursive procedure.
Iterative implementation requires maintaining a queue.
Java Code:
public static void bfs(TreeNode root) { Deque<TreeNode> queue = new ArrayDeque<TreeNode>(); queue.addLast(root); while (!queue.isEmpty()) { TreeNode cur = queue.removeFirst(); System.out.println(cur.getPayload()); if (cur.getLeft() != null) queue.addLast(cur.getLeft()); if (cur.getRight() != null) queue.addLast(cur.getRight()); } }