Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
193: Implementing an AVL Tree in Java
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
rightRotatemethod must take a nodey, make its left childxthe new root of that subtree, and movex's right subtree to becomey's left subtree. - The
leftRotatemethod must perform the mirror operation. - Both methods must correctly update the
heightproperty 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 ...
}
There are no comments for now.