DEV Community 👩‍💻👨‍💻

Cover image for Binary trees, a coding interview must-know
anes
anes

Posted on

Binary trees, a coding interview must-know

Here is the written code.

Introduction

After learning ReactJS and memorising your Starbucks order you feel fit for your first job as a software-dev, so you write an application and send it off to silicon valley. Soon enough, you receive and E Mail back from Meta, that you have been invited to a job interview. They tell you to bring your laptop because they want to see you code in an Object Oriented Language. The initial state of euphoria soon wears off as you realise that the all-seeing eye of Sauron demands to see you live in action and there is a realistic chance he will want to see binary trees.
The eye of Sauron
If you see yourself in that situation, fear no more, I come to your rescue.

What is a binary tree

A binary tree is an important data structure where we have nodes, which contain data. Those nodes have (at max) two children, which also hold values and children. Important to note is, that those children are not placed randomly. How they are placed is as follows: If the new value is bigger than the last node, we put it to the right, otherwise we put it to the left. That's how we store values in a way where we also know where those values will be when we search through them. An example of how that could look:
Example of binary tree

How do we search a value?

Imagine we have following tree:
Binary tree
When we try to search for a value we realise what binary trees excel at (or why they are blazingly fast, for every "modern" dev). Let's check if and where value 7 is stored in our tree. We start at the first node which holds the value 8, and check if our searched value is bigger or smaller than it. It's smaller so we move left. There we find 6. Our value is bigger than 6 so we move right and there it is, our searched value.

How are the values added?

To be able to use our search algorithm, we will also need to implement a special way on how to insert values at the correct place, otherwise our algorithm won't be of any use. Let's use our tree from before and just add the value 12:
Binary tree with 12 above it
There we do the same as we did to find the value. We start at 8, move right and arrive at 16. There we move left because it is smaller. Now we are at 13, which has no children to its left, or where we would need to go. That means, that we insert it to the left of the 13.
Binary tree with inserted value

Let's implement that in Java

After understanding this important theory you ask yourself: "Dude, just get to the point.", and I will now. I use VSCode, where I created a new Java project. In there I created two new classes: Node.java and BinaryTree.java.

Node

Let's begin by implementing the simpler part: Our node. That class will hold 3 attributes: its left child, its right child and its value. The children have the type Node, while the value will hold an int in our case:

public class Node {
    Node leftChild;
    Node rightChild;
    int value;

    public Node(int value) {
        this.value = value;
    }

    // Getter and setter   
}
Enter fullscreen mode Exit fullscreen mode

Tree

Next we will write our BinaryTree.java. That only holds 1 attribute: Node root. It's implementation looks as follows:

public class BinaryTree {
   Node root;

    public BinaryTree() {
         this.root = null;
    }

    // Getter and setter
}
Enter fullscreen mode Exit fullscreen mode

And that we initialise in our main function:

public class App {
    public static void main(String[] args) throws Exception {
        BinaryTree tree = new BinaryTree();
        tree.setRoot(new Node(10));
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementing the add node algorithm

You maybe realised, that we can't add values yet. We will implement that next. Let's create a function in our binary tree called appendNode. Now what exactly does that function need to do? It first needs to go to the root node, check if the value is smaller or bigger and then go to one of the children, depending on if it's bigger or smaller. Usually the left side holds smaller children. If our value to be added is the same as a node, we can go either side. It has no relevance as long as we stay consistent. I chose to count equal values as bigger:

public Node appendNode(int value, Node node) {
    if (node.value > value) {
        appendNode(value, node.leftChild);
    } else {
        appendNode(value, node.rightChild);
    }
}
Enter fullscreen mode Exit fullscreen mode

We will also return our newly created Node in the end, that's why I chose that return type.
What would happen if we run this function now? It would just recursively run trough and never come to a stop. To prevent that we need to implement the next feature: realise when its done searching for a free space. We achieve that by putting an if-statement that checks if that child node is null. If that's the case we found it and can set it as that child:

public Node appendNode(int value, Node node) {
    if (node.value > value) {
        if (node.leftChild == null) {
            node.setLeftChild(new Node(value));
            return node.leftChild;
        } else {
            appendNode(value, node.leftChild);
        }
    } else {
        if (node.rightChild == null) {
            node.setRightChild(new Node(value));
            return node.rightChild;
        } else {
            appendNode(value, node.rightChild);
        }
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

The return null; is just there to satisfy the compiler, it can never be reached.
Lastly, we have to think about what our initial value for our node is. To make it more comfortable to use it, we want to allow null and set the root manually. We can implement that with a simple null check:

public Node appendNode(int value, Node node) {
    if (node == null) {
        node = this.root;
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

We can check if it works by calling the function with the value 4 and checking the value of the roots leftChild:

public class App {
    public static void main(String[] args) throws Exception {
        BinaryTree tree = new BinaryTree();
        tree.setRoot(new Node(10));
        tree.appendNode(4, null);
        System.out.println(tree.getRoot().getLeftChild().getValue());
    }
}
Enter fullscreen mode Exit fullscreen mode

This should print 4, which it does. We can also rebuild the tree from above now and check if 12 is where it should be:

public class App {
    public static void main(String[] args) throws Exception {
        BinaryTree tree = new BinaryTree();
        tree.setRoot(new Node(8));
        tree.appendNode(6, null);
        tree.appendNode(3, null);
        tree.appendNode(7, null);
        tree.appendNode(16, null);
        tree.appendNode(13, null);
        tree.appendNode(23, null);
        tree.appendNode(14, null);
        // Our last node:
        tree.appendNode(12, null);
        // If we want to check if its at the same place:
        System.out.println(tree.getRoot().getRightChild().getLeftChild().getLeftChild().getValue());
    }
}
Enter fullscreen mode Exit fullscreen mode

That prints 12, which means the algorithm does work as it should.

Implementing the value search

The search is very similar to our algorithm where we added nodes, but now we search for them. Step one is, once again, to check if our value is bigger or smaller than the current nodes value:

public Node searchByValue(int value, Node node) {
    if (node.value > value) {
        searchByValue(value, node.leftChild);
    } else {
        searchByValue(value, node.rightChild);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we also need to add a check for if it is the same. In that case we will need to return the value:

public Node searchByValue(int value, Node node) {
    if (node.value == value) {
        return node;
    } else if (node.value > value) {
        searchByValue(value, node.leftChild);
    } else {
        searchByValue(value, node.rightChild);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the case that the child we are trying to get is null, we know that the value does not exist, so we can return null:

public Node searchByValue(int value, Node node) {
    if (node.value == value) {
        return node;
    } else if (node.value > value) {
        if (node.leftChild == null) {
            return null
        }
        searchByValue(value, node.leftChild);
    } else {
        if (node.rightChild == null) {
            return null
        }
        searchByValue(value, node.rightChild);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we do the last cleanup, which is setting the node to root if it is null and returning the return value from searchByValue:

public Node searchByValue(int value, Node node) {
    if (node == null) {
        node = this.root
    }

    if (node.value == value) {
        return node;
    } else if (node.value > value) {
        if (node.leftChild == null) {
            return null
        }
        return searchByValue(value, node.leftChild);
    } else {
        if (node.rightChild == null) {
            return null
        }
        return searchByValue(value, node.rightChild);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now if we run it for our binary tree from before:

public class App {
    public static void main(String[] args) throws Exception {
        BinaryTree tree = new BinaryTree();
        tree.setRoot(new Node(8));
        tree.appendNode(6, null);
        tree.appendNode(3, null);
        tree.appendNode(7, null);
        tree.appendNode(16, null);
        tree.appendNode(13, null);
        tree.appendNode(23, null);
        tree.appendNode(14, null);
        tree.appendNode(12, null);
        System.out.println(tree.searchByValue(12, null));
    }
}
Enter fullscreen mode Exit fullscreen mode

We get following output (assuming you also generated a toString method):

{
    leftChild='null',
    rightChild='null',
    value='12'
}
Enter fullscreen mode Exit fullscreen mode

Next steps (or conclusion)

Now that you don't have to fear any Corporate CEO's lizard eyes watching you make mistakes while implementing binary trees you are a big step closer to scoring your first silicon valley position. But there is still a lot of work ahead. There will probably be a few more articles like these about other coding interview topics, so stay tuned and happy hacking!

Top comments (0)

Tired of sifting through your feed?

You can change your feed and see more relevant posts by adding a rating to different tags on DEV. Head here to adjust your weights.