Binary Trees

Somehow the 2-ary trees are the most common. Really common, as it turns out.

Why Care?

A binary tree is just a $k$-ary tree with $k = 2$. Because $k$ is only 2:

These notes present binary trees abstractly

This page lays out a some basic theory, terminology, and even some mathematics surrounding binary trees. If you need examples first, You could check out the notes on binary search trees and come back to this presentation later.

The material here is still important, as it covers the traversal mechanisms so common on job interviews.

Special Traversals

Like all oriented trees, breadth-first and depth-first traversals exist for binary trees, but there are others:

bintraverse.png

  • Preorder: C F G A D E H B I J K
  • Inorder: G F D A E C I B K J H
  • Postorder: G D E A F I K J B H C

Binary Trees as Strings

In our previous notes on trees awe saw that trees could be represented as strings of the form (root child1 child2 ... childn). But this won’t work for binary trees, since we have to make clear exactly whether a tree with one child has a left child or a right child! So let’s put the root in the middle.

We can make our representation with three simple rules:

So for example:

How Many Binary Trees Are There?

There are five distinct shapes of binary trees with three nodes:

allthreenodebinarytrees.png

But how many are there for $n$ nodes?

Let $C_n$ be the number of distinct binary trees with $n$ nodes. This is equal to the number of trees that have a root, a left subtree with $j$ nodes, and a right subtree of $(n-1)-i$ nodes, for each $i$. That is:

$C_n = C_0C_{n-1} + C_1C_{n-2} + \cdots + C_{n-1}C_0$

which is:

$C_0 = 1$     and     $C_{n+1} = \sum_{i=0}^n C_iC_{n-i}$

The first few terms:

$\begin{array} \\ C_0 = 1 \\ C_1 = C_0C_0 = 1 \\ C_2 = C_0C_1 + C_1C_0 = 2 \\ C_3 = C_0C_2 + C_1C_1 + C_2C_0 = 5 \\ C_4 = C_0C_3 + C_1C_2 + C_2C_1 + C_3C_0 = 14 \end{array}$

You can prove:

$C_n = \frac{1}{n+1}{2n \choose n}$

Here's the number of 8-node binary trees:

$C_8 = \frac{1}{9} \times \frac{16!}{8!8!} = \frac{16 \times 15 \times 14 \times 13 \times 12 \times 11 \times 10}{8 \times 7 \times 6 \times 5 \times 4 \times 3 \times 2 \times 1} = 13 \times 11 \times 10 = 1430$

Also see Wikipedia's article on the Catalan Numbers.

A Starter Implementation

A binary tree is either empty or is a node with a left subtree and a right subtree.

Since these are the only two choices, let’s go with a sealed interface for our binary tree:

BinaryTree.java
import java.util.function.Consumer;

public sealed interface BinaryTree permits EmptyTree, BinaryTreeNode {
    int size();

    void preOrder(Consumer<String> consumer);

    void inOrder(Consumer<String> consumer);

    void postOrder(Consumer<String> consumer);
}

final class EmptyTree implements BinaryTree {
    public int size() {
        return 0;
    }

    public void preOrder(Consumer<String> consumer) {
        // Nothing to do in an empty tree
    }

    public void inOrder(Consumer<String> consumer) {
        // Nothing to do in an empty tree
    }

    public void postOrder(Consumer<String> consumer) {
        // Nothing to do in an empty tree
    }

    @Override
    public String toString() {
        return "()";
    }
}

final class BinaryTreeNode implements BinaryTree {
    private String data;
    private BinaryTree left;
    private BinaryTree right;

    public int size() {
        return left.size() + right.size() + 1;
    }

    @Override
    public String toString() {
        return "(" + left + data + right + ")".replace("()", "");
    }

    public void preOrder(Consumer<String> consumer) {
        consumer.accept(data);
        left.preOrder(consumer);
        right.preOrder(consumer);
    }

    public void inOrder(Consumer<String> consumer) {
        left.inOrder(consumer);
        consumer.accept(data);
        right.inOrder(consumer);
    }

    public void postOrder(Consumer<String> consumer) {
        left.postOrder(consumer);
        right.postOrder(consumer);
        consumer.accept(data);
    }
}

Here’s an app that uses them:

TODO

As usual, it is good practice to write tests:

TODO

Summary

We’ve covered:

  • Why binary trees are interesting
  • Preorder, postorder, inorder
  • String representations
  • Some totally gratuitous math to help exercise your brain
  • A basic implementation