Project 3: Trees and Recursion

(Return to homepage)

In Project 3, you'll write several recursive algorithms to traverse and perform common computations on tree data structures.

Trees are similar to Linked Lists, in that they are composed of several individual nodes carrying values, linked to one another by reference. Unliked Linked Lists, individual tree nodes may have any number (the branching factor) of "next" children nodes, each of which can have children nodes of their own, which can have their own child nodes, and so forth - with the limitation that the tree is acyclic (child nodes may no more than one parent). The tree eventually ends with (several) nodes that have no children, known as leaf nodes.

Due to branching, processing the nodes in the tree structure is more complicated than a simple linear search (plain for loop iteration). Finding a node in the tree requires examining the current node, and then examining the child nodes - and all their children! Our algorithms must both look forwards, down a particular path, and be able to backtrack when necessary.

The solution to this branching problem is recursion. Recursion involves dividing bigger problems into smaller problems, until they become small enough to be trivially solved (the base case). Then, the solutions to the smaller problems are combined to form a solution to the larger problem (the recursive case). Refer to the class notes for examples of Java recursion.

Trees and recursion go hand-in-hand: given the root node of a tree, a child of a tree node is itself a root node of a (sub)tree of the original tree. Recursive tree algorithms treat child nodes as subproblems and combine the recursive calls to compute a solution for the overall tree. In this project, you'll get to implement several recursive (and one iterative) tree algorithms, which are coincidentally quite common as technical interview problems as well.

Let's get to work. Get the Project 3 starter code here! (jUnit dependencies included)

The project code should work out-of-the-box for IntelliJ (running and debugging), but if you're having trouble getting it working, try these IntelliJ setup instructions.

Binary Tree Algorithms

Binary Trees are trees with a branching factor of 2, that is, each node may have up to two children. In this project, we compose binary trees using the BinaryNode<T> class. Each BinaryNode has a payload, and references to BinaryNode children left and right.

The code for all Binary Tree algorithms will be written as static methods contained in trees::BinaryTreeAlgorithms.

Depth-First Binary Tree Traversal

A depth-first search involves examining the deepest child nodes of a tree before examining neighboring child nodes. In real life, this is like walking to the back of a grocery store first, instead of scanning the first item in each aisle. With Binary Trees, there are three ways to perform depth-first search, which you will implement in this project.

Hint: All three algorithms are nearly identical and not complicated. The minimal reference solution for each method body is seven lines of code, and has no loops.

Hint: The List interface is implemented by both Java LinkedList and ArrayList. You may use the addAll(...) method to copy and append all the values of another list onto a list.

Binary Search Tree

A Binary Search Tree is a special kind of Binary Tree that enables efficient O(logn)search and lookup. Binary Search Trees have the constraint that all nodes in the left subtree have payloads less than the parent payload, and all nodes in the right subtree have payloads greater than the parent payload.

The diagram above is an example of a Binary Search Tree. Searching a BST is efficient because we always know which child to search to look for the target value: left if the current root value is too high, and right if the current root value is too low. The performance of binary search is O(logn) because with every step, the amount of nodes remaining to search is halved.

Other Recursive Binary Tree Algorithms

Hint: The path(...) method is harder than the previous algorithms to implement. Consider trying the other algorithms below first to get more practice with recursion.

Tree Algorithms

Tree algorithms are slightly more complex than binary tree algorithms, as they must account for an arbitrary number of children (compared to up to two children for binary trees). The TreeNode<T> class is the internal node that makes up trees in this project; child nodes are stored in an ArrayList property named children.

Fortunately, the algorithms themselves follow the same recurisve pattern. The only difference now is that you'll need to iterate over all children for the recursive case (use the for-each loop!) and combine their results. Once you figure out one of them, you pretty much have all of them.

Running Your Code + Bonus Points

As with the previous projects, this project contains a full test suite (see the test package) that will evaluate your code.

This project contains 15 test cases, one for each method. You only need to pass 14 of the test cases to get a score of 100. If you can pass all 15, you will get 10 bonus points for a total score of 110.

A run script for the entire project test suite is included as run.bat (Windows) or run.sh (Mac/Linux). This will compile your entire project and produce a run report with your estimated grade, including bonus points. Your entire project must compile for the script to work, and your entire project must compile to receive a grade after submission.

You are welcome to modify the tests for debugging purposes, though keep in mind your project will be graded using an unmodified copy of the test suite.

Submission Instructions

See the submissions page for details. Have questions or need help? Post on Piazza!

Critical Analysis Questions

These questions aren't graded, but are similar to those you may see on an exam. You are welcome to discuss these with classmates offline on on Piazza.

(Return to homepage)