Skip to Content
Course content

193: Implementing an AVL Tree in Java

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When people first encounter AVL trees, they often fall into a trap I call the "Batch Balancing" myth. They assume that since a standard Binary Search Tree (BST) can become skewed—like a linked list—the solution is to write a method that periodically scans the whole tree and "re-balances" it. I've seen students try to implement a balanceAll() function that triggers every 100 insertions.

The "Balance Later" Myth

If you try to balance a tree in batches, you've fundamentally misunderstood why we use AVL trees. Let's look at why. Imagine you're inserting a sorted sequence of IDs: 10, 20, 30, 40, 50. In a standard BST, this creates a straight line. If you wait until you have 1,000 nodes before balancing, every search operation during that window is $O(n)$. More importantly, a global re-balance usually requires $O(n)$ time. If you're doing an $O(n)$ operation frequently, you've killed the $O(\log n)$ performance guarantee that makes the AVL tree valuable in the first place.

Maintaining Equilibrium on the Way Up

The secret to a real AVL tree is that it doesn't "batch" balance; it balances incrementally. Every single time you insert or delete a node, you backtrack from the point of change up to the root. As you unwind the recursion, you check the "Balance Factor" (the difference between the heights of the left and right subtrees). If that factor ever hits 2 or -2, you fix it right then and there. This keeps the height strictly logarithmic without ever needing a full-tree scan.

Here is how I typically structure the node to make this efficient. Don't calculate the height on the fly—that's too expensive. Store it.

class Node {
    int key, height;
    Node left, right;

    Node(int d) {
        key = d;
        height = 1;
    }
}

Handling the Four Rotation Scenarios

The actual "fixing" happens through rotations. This is where most developers get tripped up because they try to visualize it as moving a whole tree. Instead, think of it as picking a new "pivot" node to become the parent. There are four cases, but they really boil down to two types of rotations: Single and Double.

  • Right Rotation (LL Case): The left child is too heavy, and its left child is also heavy. A single right turn fixes it.
  • Left Rotation (RR Case): The right child is too heavy, and its right child is also heavy. A single left turn fixes it.
  • Left-Right Rotation (LR Case): The left child is heavy, but its right child is the one causing the imbalance. You rotate the child left, then the parent right.
  • Right-Left Rotation (RL Case): The mirror of the LR case.

I'll show you the implementation of the insert logic. Notice how the balancing happens after the recursive call returns. We are fixing the tree as we climb back up the call stack.

public Node insert(Node node, int key) {
    // 1. Standard BST insertion
    if (node == null) return new Node(key);

    if (key < node.key)
        node.left = insert(node.left, key);
    else if (key > node.key)
        node.right = insert(node.right, key);
    else 
        return node; // Duplicate keys not allowed

    // 2. Update height of this ancestor node
    node.height = 1 + Math.max(height(node.left), height(node.right));

    // 3. Get balance factor to check for imbalance
    int balance = getBalance(node);

    // Case 1: Left Left
    if (balance > 1 && key < node.left.key)
        return rightRotate(node);

    // Case 2: Right Right
    if (balance < -1 && key > node.right.key)
        return leftRotate(node);

    // Case 3: Left Right
    if (balance > 1 && key > node.left.key) {
        node.left = leftRotate(node.left);
        return rightRotate(node);
    }

    // Case 4: Right Left
    if (balance < -1 && key < node.right.key) {
        node.right = rightRotate(node.right);
        return leftRotate(node);
    }

    return node;
}

One pro tip: always implement your height() helper method to handle null nodes by returning 0. If you don't, you'll be chasing NullPointerExceptions for an hour, and it's always the same mistake.




📋 Practical Task

Implementing the Rotation Logic for a High-Frequency Trading Order Book

In high-frequency trading, order books need to stay balanced to ensure that finding the best bid or ask price happens in constant or logarithmic time. You have been provided with a skeleton of an OrderTree (an AVL Tree).

The insert method is already written, but the rotation logic is missing. Your task is to implement the rightRotate(Node y) and leftRotate(Node x) methods.

Requirements:

  • The rightRotate method must take a node y, make its left child x the new root of that subtree, and move x's right subtree to become y's left subtree.
  • The leftRotate method must perform the mirror operation.
  • Both methods must correctly update the height property of the nodes involved before returning the new root of the subtree.

class OrderTree {
    class Node {
        int orderId;
        int height;
        Node left, right;
        Node(int id) { this.orderId = id; this.height = 1; }
    }

    Node root;

    int height(Node n) { return n == null ? 0 : n.height; }

    // TODO: Implement rightRotate
    Node rightRotate(Node y) {
        // Your code here
    }

    // TODO: Implement leftRotate
    Node leftRotate(Node x) {
        // Your code here
    }

    // ... existing insert and getBalance methods ...
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.