From 49d0a66d8ba339a4012596885b87a0cf42307b41 Mon Sep 17 00:00:00 2001 From: harsha08-2k6 Date: Sat, 22 Nov 2025 10:26:49 +0530 Subject: [PATCH 1/2] feat: Add Binary Search Tree (BST) and AVL Tree implementations --- src/main/java/trees/AVLTree.java | 246 ++++++++++++++++++ src/main/java/trees/BinarySearchTree.java | 174 +++++++++++++ src/main/java/trees/Main.java | 33 +++ src/main/java/trees/TreeNode.java | 18 ++ src/test/java/trees/AVLTreeTest.java | 89 +++++++ src/test/java/trees/BinarySearchTreeTest.java | 74 ++++++ 6 files changed, 634 insertions(+) create mode 100644 src/main/java/trees/AVLTree.java create mode 100644 src/main/java/trees/BinarySearchTree.java create mode 100644 src/main/java/trees/Main.java create mode 100644 src/main/java/trees/TreeNode.java create mode 100644 src/test/java/trees/AVLTreeTest.java create mode 100644 src/test/java/trees/BinarySearchTreeTest.java diff --git a/src/main/java/trees/AVLTree.java b/src/main/java/trees/AVLTree.java new file mode 100644 index 000000000000..b6a8864e30a4 --- /dev/null +++ b/src/main/java/trees/AVLTree.java @@ -0,0 +1,246 @@ +package trees; + +/** + * AVL Tree Implementation (Self-Balancing Binary Search Tree) + * Maintains balance factor to ensure O(log n) operations + * Supports: insert, delete, search, rotations (LL, RR, LR, RL) + */ +public class AVLTree { + private TreeNode root; + + public AVLTree() { + this.root = null; + } + + // ============= HEIGHT MANAGEMENT ============= + private int getHeight(TreeNode node) { + return node == null ? 0 : node.height; + } + + private int getBalance(TreeNode node) { + return node == null ? 0 : getHeight(node.left) - getHeight(node.right); + } + + private void updateHeight(TreeNode node) { + if (node != null) { + node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); + } + } + + // ============= ROTATION OPERATIONS ============= + // Right Rotation (LL case) + private TreeNode rotateRight(TreeNode y) { + TreeNode x = y.left; + TreeNode T2 = x.right; + + x.right = y; + y.left = T2; + + updateHeight(y); + updateHeight(x); + + return x; + } + + // Left Rotation (RR case) + private TreeNode rotateLeft(TreeNode x) { + TreeNode y = x.right; + TreeNode T2 = y.left; + + y.left = x; + x.right = T2; + + updateHeight(x); + updateHeight(y); + + return y; + } + + // ============= INSERT OPERATION ============= + public void insert(int value) { + root = insertRecursive(root, value); + } + + private TreeNode insertRecursive(TreeNode node, int value) { + if (node == null) { + return new TreeNode(value); + } + + if (value < node.value) { + node.left = insertRecursive(node.left, value); + } else if (value > node.value) { + node.right = insertRecursive(node.right, value); + } else { + return node; // Duplicate ignored + } + + updateHeight(node); + return balance(node); + } + + // ============= BALANCE OPERATION ============= + private TreeNode balance(TreeNode node) { + int balanceFactor = getBalance(node); + + // Left Heavy Cases + if (balanceFactor > 1) { + if (getBalance(node.left) < 0) { + // LR case: Left-Right + node.left = rotateLeft(node.left); + } + // LL case: Left-Left + return rotateRight(node); + } + + // Right Heavy Cases + if (balanceFactor < -1) { + if (getBalance(node.right) > 0) { + // RL case: Right-Left + node.right = rotateRight(node.right); + } + // RR case: Right-Right + return rotateLeft(node); + } + + return node; + } + + // ============= DELETE OPERATION ============= + public void delete(int value) { + root = deleteRecursive(root, value); + } + + private TreeNode deleteRecursive(TreeNode node, int value) { + if (node == null) { + return null; + } + + if (value < node.value) { + node.left = deleteRecursive(node.left, value); + } else if (value > node.value) { + node.right = deleteRecursive(node.right, value); + } else { + // Node to delete found + if (node.left == null && node.right == null) { + return null; + } + if (node.left == null) { + return node.right; + } + if (node.right == null) { + return node.left; + } + + TreeNode minRight = findMinNode(node.right); + node.value = minRight.value; + node.right = deleteRecursive(node.right, minRight.value); + } + + updateHeight(node); + return balance(node); + } + + // ============= SEARCH OPERATION ============= + public boolean search(int value) { + return searchRecursive(root, value); + } + + private boolean searchRecursive(TreeNode node, int value) { + if (node == null) { + return false; + } + + if (value == node.value) { + return true; + } else if (value < node.value) { + return searchRecursive(node.left, value); + } else { + return searchRecursive(node.right, value); + } + } + + // ============= UTILITY METHODS ============= + public int findMin() { + if (root == null) { + throw new IllegalStateException("Tree is empty"); + } + return findMinNode(root).value; + } + + private TreeNode findMinNode(TreeNode node) { + while (node.left != null) { + node = node.left; + } + return node; + } + + public int findMax() { + if (root == null) { + throw new IllegalStateException("Tree is empty"); + } + return findMaxNode(root).value; + } + + private TreeNode findMaxNode(TreeNode node) { + while (node.right != null) { + node = node.right; + } + return node; + } + + // ============= TREE TRAVERSALS ============= + public void inorder() { + System.out.print("Inorder: "); + inorderRecursive(root); + System.out.println(); + } + + private void inorderRecursive(TreeNode node) { + if (node != null) { + inorderRecursive(node.left); + System.out.print(node.value + " "); + inorderRecursive(node.right); + } + } + + public void preorder() { + System.out.print("Preorder: "); + preorderRecursive(root); + System.out.println(); + } + + private void preorderRecursive(TreeNode node) { + if (node != null) { + System.out.print(node.value + " "); + preorderRecursive(node.left); + preorderRecursive(node.right); + } + } + + public void postorder() { + System.out.print("Postorder: "); + postorderRecursive(root); + System.out.println(); + } + + private void postorderRecursive(TreeNode node) { + if (node != null) { + postorderRecursive(node.left); + postorderRecursive(node.right); + System.out.print(node.value + " "); + } + } + + // ============= HELPER METHODS ============= + public int getHeight() { + return getHeight(root); + } + + public boolean isEmpty() { + return root == null; + } + + public void clear() { + root = null; + } +} \ No newline at end of file diff --git a/src/main/java/trees/BinarySearchTree.java b/src/main/java/trees/BinarySearchTree.java new file mode 100644 index 000000000000..8b98f1ec1ed2 --- /dev/null +++ b/src/main/java/trees/BinarySearchTree.java @@ -0,0 +1,174 @@ +package trees; + +/** + * Binary Search Tree Implementation + * Supports: insert, delete, search, traversals (inorder, preorder, postorder) + * Duplicate handling: Duplicates are ignored (not inserted) + */ +public class BinarySearchTree { + private TreeNode root; + + public BinarySearchTree() { + this.root = null; + } + + // ============= INSERT OPERATION ============= + public void insert(int value) { + root = insertRecursive(root, value); + } + + private TreeNode insertRecursive(TreeNode node, int value) { + if (node == null) { + return new TreeNode(value); + } + + if (value < node.value) { + node.left = insertRecursive(node.left, value); + } else if (value > node.value) { + node.right = insertRecursive(node.right, value); + } + // If value == node.value, duplicate is ignored + + return node; + } + + // ============= SEARCH OPERATION ============= + public boolean search(int value) { + return searchRecursive(root, value); + } + + private boolean searchRecursive(TreeNode node, int value) { + if (node == null) { + return false; + } + + if (value == node.value) { + return true; + } else if (value < node.value) { + return searchRecursive(node.left, value); + } else { + return searchRecursive(node.right, value); + } + } + + // ============= DELETE OPERATION ============= + public void delete(int value) { + root = deleteRecursive(root, value); + } + + private TreeNode deleteRecursive(TreeNode node, int value) { + if (node == null) { + return null; + } + + if (value < node.value) { + node.left = deleteRecursive(node.left, value); + } else if (value > node.value) { + node.right = deleteRecursive(node.right, value); + } else { + // Node to delete found + + // Case 1: Node has no children (leaf) + if (node.left == null && node.right == null) { + return null; + } + + // Case 2: Node has one child + if (node.left == null) { + return node.right; + } + if (node.right == null) { + return node.left; + } + + // Case 3: Node has two children + // Find inorder successor (smallest in right subtree) + TreeNode minRight = findMin(node.right); + node.value = minRight.value; + node.right = deleteRecursive(node.right, minRight.value); + } + + return node; + } + + // ============= UTILITY METHODS ============= + public int findMin() { + if (root == null) { + throw new IllegalStateException("Tree is empty"); + } + return findMin(root).value; + } + + private TreeNode findMin(TreeNode node) { + while (node.left != null) { + node = node.left; + } + return node; + } + + public int findMax() { + if (root == null) { + throw new IllegalStateException("Tree is empty"); + } + return findMax(root).value; + } + + private TreeNode findMax(TreeNode node) { + while (node.right != null) { + node = node.right; + } + return node; + } + + // ============= TREE TRAVERSALS ============= + public void inorder() { + System.out.print("Inorder: "); + inorderRecursive(root); + System.out.println(); + } + + private void inorderRecursive(TreeNode node) { + if (node != null) { + inorderRecursive(node.left); + System.out.print(node.value + " "); + inorderRecursive(node.right); + } + } + + public void preorder() { + System.out.print("Preorder: "); + preorderRecursive(root); + System.out.println(); + } + + private void preorderRecursive(TreeNode node) { + if (node != null) { + System.out.print(node.value + " "); + preorderRecursive(node.left); + preorderRecursive(node.right); + } + } + + public void postorder() { + System.out.print("Postorder: "); + postorderRecursive(root); + System.out.println(); + } + + private void postorderRecursive(TreeNode node) { + if (node != null) { + postorderRecursive(node.left); + postorderRecursive(node.right); + System.out.print(node.value + " "); + } + } + + // ============= HELPER METHODS ============= + public boolean isEmpty() { + return root == null; + } + + public void clear() { + root = null; + } +} \ No newline at end of file diff --git a/src/main/java/trees/Main.java b/src/main/java/trees/Main.java new file mode 100644 index 000000000000..76764daf847f --- /dev/null +++ b/src/main/java/trees/Main.java @@ -0,0 +1,33 @@ +package trees; + +public class Main { + public static void main(String[] args) { + System.out.println("===== BINARY SEARCH TREE DEMO =====\n"); + BinarySearchTree bst = new BinarySearchTree(); + + int[] values = {50, 30, 70, 20, 40, 60, 80}; + for (int val : values) { + bst.insert(val); + } + + System.out.println("Inserted: " + java.util.Arrays.toString(values)); + bst.inorder(); + bst.preorder(); + bst.postorder(); + System.out.println("Min: " + bst.findMin() + ", Max: " + bst.findMax()); + + System.out.println("\n===== AVL TREE DEMO =====\n"); + AVLTree avl = new AVLTree(); + + for (int val : values) { + avl.insert(val); + } + + System.out.println("Inserted: " + java.util.Arrays.toString(values)); + avl.inorder(); + System.out.println("Tree Height: " + avl.getHeight()); + avl.preorder(); + avl.postorder(); + System.out.println("Min: " + avl.findMin() + ", Max: " + avl.findMax()); + } +} \ No newline at end of file diff --git a/src/main/java/trees/TreeNode.java b/src/main/java/trees/TreeNode.java new file mode 100644 index 000000000000..0eb823c74e84 --- /dev/null +++ b/src/main/java/trees/TreeNode.java @@ -0,0 +1,18 @@ +package trees; + +/** + * Generic node class for tree implementations + */ +public class TreeNode { + public int value; + public TreeNode left; + public TreeNode right; + public int height; // Used for AVL Tree balancing + + public TreeNode(int value) { + this.value = value; + this.left = null; + this.right = null; + this.height = 1; + } +} \ No newline at end of file diff --git a/src/test/java/trees/AVLTreeTest.java b/src/test/java/trees/AVLTreeTest.java new file mode 100644 index 000000000000..0b70d1909661 --- /dev/null +++ b/src/test/java/trees/AVLTreeTest.java @@ -0,0 +1,89 @@ +package trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AVLTreeTest { + private AVLTree avl; + + @BeforeEach + public void setUp() { + avl = new AVLTree(); + } + + @Test + public void testInsertAndSearch() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + + assertTrue(avl.search(50)); + assertTrue(avl.search(30)); + assertTrue(avl.search(70)); + assertFalse(avl.search(100)); + } + + @Test + public void testBalancing() { + // Insert values in ascending order (would create unbalanced BST) + avl.insert(10); + avl.insert(20); + avl.insert(30); + avl.insert(40); + avl.insert(50); + + // AVL should balance - height should be log(n) + int height = avl.getHeight(); + int n = 5; + int maxHeight = (int) (Math.log(n + 1) / Math.log(2)) + 1; + + assertTrue(height <= maxHeight); + } + + @Test + public void testDelete() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + + avl.delete(20); + assertFalse(avl.search(20)); + + avl.delete(30); + assertFalse(avl.search(30)); + } + + @Test + public void testRotations() { + // Test LL rotation case + AVLTree ll = new AVLTree(); + ll.insert(30); + ll.insert(20); + ll.insert(10); + assertTrue(ll.search(10) && ll.search(20) && ll.search(30)); + + // Test RR rotation case + AVLTree rr = new AVLTree(); + rr.insert(10); + rr.insert(20); + rr.insert(30); + assertTrue(rr.search(10) && rr.search(20) && rr.search(30)); + } + + @Test + public void testFindMinMax() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(80); + + assertEquals(20, avl.findMin()); + assertEquals(80, avl.findMax()); + } +} \ No newline at end of file diff --git a/src/test/java/trees/BinarySearchTreeTest.java b/src/test/java/trees/BinarySearchTreeTest.java new file mode 100644 index 000000000000..0256b148cb54 --- /dev/null +++ b/src/test/java/trees/BinarySearchTreeTest.java @@ -0,0 +1,74 @@ +package trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BinarySearchTreeTest { + private BinarySearchTree bst; + + @BeforeEach + public void setUp() { + bst = new BinarySearchTree(); + } + + @Test + public void testInsertAndSearch() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + + assertTrue(bst.search(50)); + assertTrue(bst.search(30)); + assertTrue(bst.search(70)); + assertFalse(bst.search(100)); + } + + @Test + public void testDuplicateHandling() { + bst.insert(50); + bst.insert(50); + bst.insert(50); + + assertTrue(bst.search(50)); + } + + @Test + public void testDelete() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + + bst.delete(20); // Leaf node + assertFalse(bst.search(20)); + + bst.delete(30); // Node with two children + assertFalse(bst.search(30)); + + bst.delete(50); // Root node + assertFalse(bst.search(50)); + } + + @Test + public void testFindMinMax() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(80); + + assertEquals(20, bst.findMin()); + assertEquals(80, bst.findMax()); + } + + @Test + public void testEmptyTreeException() { + assertThrows(IllegalStateException.class, () -> bst.findMin()); + assertThrows(IllegalStateException.class, () -> bst.findMax()); + } +} \ No newline at end of file From 83fa41fac969a14b4bf834a004930c4a00c98cb2 Mon Sep 17 00:00:00 2001 From: harsha08-2k6 Date: Sat, 22 Nov 2025 10:52:33 +0530 Subject: [PATCH 2/2] feat: Add Binary Search Tree and AVL Tree implementations- Implement BinarySearchTree with insert, delete, search operations- Add inorder, preorder, postorder traversals- Implement AVLTree with self-balancing rotations- Add comprehensive JUnit 5 tests- All code formatted and documented --- src/main/java/trees/AVLTree.java | 489 +++++++++--------- src/main/java/trees/BinarySearchTree.java | 319 ++++++------ src/test/java/trees/AVLTreeTest.java | 174 ++++--- src/test/java/trees/BinarySearchTreeTest.java | 142 ++--- 4 files changed, 589 insertions(+), 535 deletions(-) diff --git a/src/main/java/trees/AVLTree.java b/src/main/java/trees/AVLTree.java index b6a8864e30a4..0d6548c7423c 100644 --- a/src/main/java/trees/AVLTree.java +++ b/src/main/java/trees/AVLTree.java @@ -1,246 +1,259 @@ +// File 3: AVLTree.java package trees; /** * AVL Tree Implementation (Self-Balancing Binary Search Tree) - * Maintains balance factor to ensure O(log n) operations - * Supports: insert, delete, search, rotations (LL, RR, LR, RL) + * + * An AVL Tree is a self-balancing BST where the heights of the two child + * subtrees of any node differ by at most one. This guarantees O(log n) + * complexity for all operations by performing rotations after each + * insertion/deletion. + * + * Reference: https://en.wikipedia.org/wiki/AVL_tree + * + * Rotations Performed: - LL Rotation (Left-Left case): Right rotation - RR + * Rotation (Right-Right case): Left rotation - LR Rotation (Left-Right case): + * Left rotation followed by right rotation - RL Rotation (Right-Left case): + * Right rotation followed by left rotation */ public class AVLTree { - private TreeNode root; - - public AVLTree() { - this.root = null; - } - - // ============= HEIGHT MANAGEMENT ============= - private int getHeight(TreeNode node) { - return node == null ? 0 : node.height; - } - - private int getBalance(TreeNode node) { - return node == null ? 0 : getHeight(node.left) - getHeight(node.right); - } - - private void updateHeight(TreeNode node) { - if (node != null) { - node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); - } - } - - // ============= ROTATION OPERATIONS ============= - // Right Rotation (LL case) - private TreeNode rotateRight(TreeNode y) { - TreeNode x = y.left; - TreeNode T2 = x.right; - - x.right = y; - y.left = T2; - - updateHeight(y); - updateHeight(x); - - return x; - } - - // Left Rotation (RR case) - private TreeNode rotateLeft(TreeNode x) { - TreeNode y = x.right; - TreeNode T2 = y.left; - - y.left = x; - x.right = T2; - - updateHeight(x); - updateHeight(y); - - return y; - } - - // ============= INSERT OPERATION ============= - public void insert(int value) { - root = insertRecursive(root, value); - } - - private TreeNode insertRecursive(TreeNode node, int value) { - if (node == null) { - return new TreeNode(value); - } - - if (value < node.value) { - node.left = insertRecursive(node.left, value); - } else if (value > node.value) { - node.right = insertRecursive(node.right, value); - } else { - return node; // Duplicate ignored - } - - updateHeight(node); - return balance(node); - } - - // ============= BALANCE OPERATION ============= - private TreeNode balance(TreeNode node) { - int balanceFactor = getBalance(node); - - // Left Heavy Cases - if (balanceFactor > 1) { - if (getBalance(node.left) < 0) { - // LR case: Left-Right - node.left = rotateLeft(node.left); - } - // LL case: Left-Left - return rotateRight(node); - } - - // Right Heavy Cases - if (balanceFactor < -1) { - if (getBalance(node.right) > 0) { - // RL case: Right-Left - node.right = rotateRight(node.right); - } - // RR case: Right-Right - return rotateLeft(node); - } - - return node; - } - - // ============= DELETE OPERATION ============= - public void delete(int value) { - root = deleteRecursive(root, value); - } - - private TreeNode deleteRecursive(TreeNode node, int value) { - if (node == null) { - return null; - } - - if (value < node.value) { - node.left = deleteRecursive(node.left, value); - } else if (value > node.value) { - node.right = deleteRecursive(node.right, value); - } else { - // Node to delete found - if (node.left == null && node.right == null) { - return null; - } - if (node.left == null) { - return node.right; - } - if (node.right == null) { - return node.left; - } - - TreeNode minRight = findMinNode(node.right); - node.value = minRight.value; - node.right = deleteRecursive(node.right, minRight.value); - } - - updateHeight(node); - return balance(node); - } - - // ============= SEARCH OPERATION ============= - public boolean search(int value) { - return searchRecursive(root, value); - } - - private boolean searchRecursive(TreeNode node, int value) { - if (node == null) { - return false; - } - - if (value == node.value) { - return true; - } else if (value < node.value) { - return searchRecursive(node.left, value); - } else { - return searchRecursive(node.right, value); - } - } - - // ============= UTILITY METHODS ============= - public int findMin() { - if (root == null) { - throw new IllegalStateException("Tree is empty"); - } - return findMinNode(root).value; - } - - private TreeNode findMinNode(TreeNode node) { - while (node.left != null) { - node = node.left; - } - return node; - } - - public int findMax() { - if (root == null) { - throw new IllegalStateException("Tree is empty"); - } - return findMaxNode(root).value; - } - - private TreeNode findMaxNode(TreeNode node) { - while (node.right != null) { - node = node.right; - } - return node; - } - - // ============= TREE TRAVERSALS ============= - public void inorder() { - System.out.print("Inorder: "); - inorderRecursive(root); - System.out.println(); - } - - private void inorderRecursive(TreeNode node) { - if (node != null) { - inorderRecursive(node.left); - System.out.print(node.value + " "); - inorderRecursive(node.right); - } - } - - public void preorder() { - System.out.print("Preorder: "); - preorderRecursive(root); - System.out.println(); - } - - private void preorderRecursive(TreeNode node) { - if (node != null) { - System.out.print(node.value + " "); - preorderRecursive(node.left); - preorderRecursive(node.right); - } - } - - public void postorder() { - System.out.print("Postorder: "); - postorderRecursive(root); - System.out.println(); - } - - private void postorderRecursive(TreeNode node) { - if (node != null) { - postorderRecursive(node.left); - postorderRecursive(node.right); - System.out.print(node.value + " "); - } - } - - // ============= HELPER METHODS ============= - public int getHeight() { - return getHeight(root); - } - - public boolean isEmpty() { - return root == null; - } - - public void clear() { - root = null; + private TreeNode root; + private static final String EMPTY_TREE_ERROR = "Tree is empty"; + + public AVLTree() { + this.root = null; + } + + // ============= HEIGHT MANAGEMENT ============= + private int getHeight(TreeNode node) { + return node == null ? 0 : node.height; + } + + private int getBalance(TreeNode node) { + return node == null ? 0 : getHeight(node.left) - getHeight(node.right); + } + + private void updateHeight(TreeNode node) { + if (node != null) { + node.height = + 1 + Math.max(getHeight(node.left), getHeight(node.right)); + } + } + + // ============= ROTATION OPERATIONS ============= + // Right Rotation (LL case) + private TreeNode rotateRight(TreeNode y) { + TreeNode x = y.left; + TreeNode t2 = x.right; + + x.right = y; + y.left = t2; + + updateHeight(y); + updateHeight(x); + + return x; + } + + // Left Rotation (RR case) + private TreeNode rotateLeft(TreeNode x) { + TreeNode y = x.right; + TreeNode t2 = y.left; + + y.left = x; + x.right = t2; + + updateHeight(x); + updateHeight(y); + + return y; + } + + // ============= INSERT OPERATION ============= + public void insert(int value) { + root = insertRecursive(root, value); + } + + private TreeNode insertRecursive(TreeNode node, int value) { + if (node == null) { + return new TreeNode(value); + } + + if (value < node.value) { + node.left = insertRecursive(node.left, value); + } else if (value > node.value) { + node.right = insertRecursive(node.right, value); + } else { + return node; // Duplicate ignored + } + + updateHeight(node); + return balance(node); + } + + // ============= BALANCE OPERATION ============= + private TreeNode balance(TreeNode node) { + int balanceFactor = getBalance(node); + + // Left Heavy Cases + if (balanceFactor > 1) { + if (getBalance(node.left) < 0) { + // LR case: Left-Right + node.left = rotateLeft(node.left); + } + // LL case: Left-Left + return rotateRight(node); + } + + // Right Heavy Cases + if (balanceFactor < -1) { + if (getBalance(node.right) > 0) { + // RL case: Right-Left + node.right = rotateRight(node.right); + } + // RR case: Right-Right + return rotateLeft(node); + } + + return node; + } + + // ============= DELETE OPERATION ============= + public void delete(int value) { + root = deleteRecursive(root, value); + } + + private TreeNode deleteRecursive(TreeNode node, int value) { + if (node == null) { + return null; + } + + if (value < node.value) { + node.left = deleteRecursive(node.left, value); + } else if (value > node.value) { + node.right = deleteRecursive(node.right, value); + } else { + // Node to delete found + if (node.left == null && node.right == null) { + return null; + } + if (node.left == null) { + return node.right; + } + if (node.right == null) { + return node.left; + } + + TreeNode minRight = findMinNode(node.right); + node.value = minRight.value; + node.right = deleteRecursive(node.right, minRight.value); + } + + updateHeight(node); + return balance(node); + } + + // ============= SEARCH OPERATION ============= + public boolean search(int value) { + return searchRecursive(root, value); + } + + private boolean searchRecursive(TreeNode node, int value) { + if (node == null) { + return false; + } + + if (value == node.value) { + return true; + } else if (value < node.value) { + return searchRecursive(node.left, value); + } else { + return searchRecursive(node.right, value); + } + } + + // ============= UTILITY METHODS ============= + public int findMin() { + if (root == null) { + throw new IllegalStateException(EMPTY_TREE_ERROR); + } + return findMinNode(root).value; + } + + private TreeNode findMinNode(TreeNode node) { + while (node.left != null) { + node = node.left; + } + return node; + } + + public int findMax() { + if (root == null) { + throw new IllegalStateException(EMPTY_TREE_ERROR); } + return findMaxNode(root).value; + } + + private TreeNode findMaxNode(TreeNode node) { + while (node.right != null) { + node = node.right; + } + return node; + } + + // ============= TREE TRAVERSALS ============= + public void inorder() { + System.out.print("Inorder: "); + inorderRecursive(root); + System.out.println(); + } + + private void inorderRecursive(TreeNode node) { + if (node != null) { + inorderRecursive(node.left); + System.out.print(node.value + " "); + inorderRecursive(node.right); + } + } + + public void preorder() { + System.out.print("Preorder: "); + preorderRecursive(root); + System.out.println(); + } + + private void preorderRecursive(TreeNode node) { + if (node != null) { + System.out.print(node.value + " "); + preorderRecursive(node.left); + preorderRecursive(node.right); + } + } + + public void postorder() { + System.out.print("Postorder: "); + postorderRecursive(root); + System.out.println(); + } + + private void postorderRecursive(TreeNode node) { + if (node != null) { + postorderRecursive(node.left); + postorderRecursive(node.right); + System.out.print(node.value + " "); + } + } + + // ============= HELPER METHODS ============= + public int getHeight() { + return getHeight(root); + } + + public boolean isEmpty() { + return root == null; + } + + public void clear() { + root = null; + } } \ No newline at end of file diff --git a/src/main/java/trees/BinarySearchTree.java b/src/main/java/trees/BinarySearchTree.java index 8b98f1ec1ed2..6abad2bc4da8 100644 --- a/src/main/java/trees/BinarySearchTree.java +++ b/src/main/java/trees/BinarySearchTree.java @@ -1,174 +1,183 @@ +// File 2: BinarySearchTree.java package trees; /** * Binary Search Tree Implementation - * Supports: insert, delete, search, traversals (inorder, preorder, postorder) - * Duplicate handling: Duplicates are ignored (not inserted) + * + * A Binary Search Tree (BST) is a data structure that maintains sorted data + * and enables O(log n) search, insertion, and deletion operations on average. + * + * Reference: https://en.wikipedia.org/wiki/Binary_search_tree + * + * Operations: - Insert: Add new value maintaining BST property - Delete: Remove + * value while maintaining BST property - Search: Find value in O(log n) average + * time - Traversals: Inorder, Preorder, Postorder */ public class BinarySearchTree { - private TreeNode root; - - public BinarySearchTree() { - this.root = null; - } - - // ============= INSERT OPERATION ============= - public void insert(int value) { - root = insertRecursive(root, value); - } - - private TreeNode insertRecursive(TreeNode node, int value) { - if (node == null) { - return new TreeNode(value); - } - - if (value < node.value) { - node.left = insertRecursive(node.left, value); - } else if (value > node.value) { - node.right = insertRecursive(node.right, value); - } - // If value == node.value, duplicate is ignored - - return node; - } - - // ============= SEARCH OPERATION ============= - public boolean search(int value) { - return searchRecursive(root, value); - } - - private boolean searchRecursive(TreeNode node, int value) { - if (node == null) { - return false; - } - - if (value == node.value) { - return true; - } else if (value < node.value) { - return searchRecursive(node.left, value); - } else { - return searchRecursive(node.right, value); - } - } - - // ============= DELETE OPERATION ============= - public void delete(int value) { - root = deleteRecursive(root, value); - } - - private TreeNode deleteRecursive(TreeNode node, int value) { - if (node == null) { - return null; - } - - if (value < node.value) { - node.left = deleteRecursive(node.left, value); - } else if (value > node.value) { - node.right = deleteRecursive(node.right, value); - } else { - // Node to delete found - - // Case 1: Node has no children (leaf) - if (node.left == null && node.right == null) { - return null; - } - - // Case 2: Node has one child - if (node.left == null) { - return node.right; - } - if (node.right == null) { - return node.left; - } - - // Case 3: Node has two children - // Find inorder successor (smallest in right subtree) - TreeNode minRight = findMin(node.right); - node.value = minRight.value; - node.right = deleteRecursive(node.right, minRight.value); - } - - return node; - } - - // ============= UTILITY METHODS ============= - public int findMin() { - if (root == null) { - throw new IllegalStateException("Tree is empty"); - } - return findMin(root).value; + private TreeNode root; + private static final String EMPTY_TREE_ERROR = "Tree is empty"; + + public BinarySearchTree() { + this.root = null; + } + + // ============= INSERT OPERATION ============= + public void insert(int value) { + root = insertRecursive(root, value); + } + + private TreeNode insertRecursive(TreeNode node, int value) { + if (node == null) { + return new TreeNode(value); } - - private TreeNode findMin(TreeNode node) { - while (node.left != null) { - node = node.left; - } - return node; + + if (value < node.value) { + node.left = insertRecursive(node.left, value); + } else if (value > node.value) { + node.right = insertRecursive(node.right, value); } - - public int findMax() { - if (root == null) { - throw new IllegalStateException("Tree is empty"); - } - return findMax(root).value; + // If value == node.value, duplicate is ignored + + return node; + } + + // ============= SEARCH OPERATION ============= + public boolean search(int value) { + return searchRecursive(root, value); + } + + private boolean searchRecursive(TreeNode node, int value) { + if (node == null) { + return false; } - - private TreeNode findMax(TreeNode node) { - while (node.right != null) { - node = node.right; - } - return node; + + if (value == node.value) { + return true; + } else if (value < node.value) { + return searchRecursive(node.left, value); + } else { + return searchRecursive(node.right, value); + } + } + + // ============= DELETE OPERATION ============= + public void delete(int value) { + root = deleteRecursive(root, value); + } + + private TreeNode deleteRecursive(TreeNode node, int value) { + if (node == null) { + return null; } - - // ============= TREE TRAVERSALS ============= - public void inorder() { - System.out.print("Inorder: "); - inorderRecursive(root); - System.out.println(); + + if (value < node.value) { + node.left = deleteRecursive(node.left, value); + } else if (value > node.value) { + node.right = deleteRecursive(node.right, value); + } else { + // Node to delete found + + // Case 1: Node has no children (leaf) + if (node.left == null && node.right == null) { + return null; + } + + // Case 2: Node has one child + if (node.left == null) { + return node.right; + } + if (node.right == null) { + return node.left; + } + + // Case 3: Node has two children + // Find inorder successor (smallest in right subtree) + TreeNode minRight = findMin(node.right); + node.value = minRight.value; + node.right = deleteRecursive(node.right, minRight.value); } - - private void inorderRecursive(TreeNode node) { - if (node != null) { - inorderRecursive(node.left); - System.out.print(node.value + " "); - inorderRecursive(node.right); - } + + return node; + } + + // ============= UTILITY METHODS ============= + public int findMin() { + if (root == null) { + throw new IllegalStateException(EMPTY_TREE_ERROR); } - - public void preorder() { - System.out.print("Preorder: "); - preorderRecursive(root); - System.out.println(); + return findMin(root).value; + } + + private TreeNode findMin(TreeNode node) { + while (node.left != null) { + node = node.left; } - - private void preorderRecursive(TreeNode node) { - if (node != null) { - System.out.print(node.value + " "); - preorderRecursive(node.left); - preorderRecursive(node.right); - } + return node; + } + + public int findMax() { + if (root == null) { + throw new IllegalStateException(EMPTY_TREE_ERROR); } - - public void postorder() { - System.out.print("Postorder: "); - postorderRecursive(root); - System.out.println(); + return findMax(root).value; + } + + private TreeNode findMax(TreeNode node) { + while (node.right != null) { + node = node.right; } - - private void postorderRecursive(TreeNode node) { - if (node != null) { - postorderRecursive(node.left); - postorderRecursive(node.right); - System.out.print(node.value + " "); - } + return node; + } + + // ============= TREE TRAVERSALS ============= + public void inorder() { + System.out.print("Inorder: "); + inorderRecursive(root); + System.out.println(); + } + + private void inorderRecursive(TreeNode node) { + if (node != null) { + inorderRecursive(node.left); + System.out.print(node.value + " "); + inorderRecursive(node.right); } - - // ============= HELPER METHODS ============= - public boolean isEmpty() { - return root == null; + } + + public void preorder() { + System.out.print("Preorder: "); + preorderRecursive(root); + System.out.println(); + } + + private void preorderRecursive(TreeNode node) { + if (node != null) { + System.out.print(node.value + " "); + preorderRecursive(node.left); + preorderRecursive(node.right); } - - public void clear() { - root = null; + } + + public void postorder() { + System.out.print("Postorder: "); + postorderRecursive(root); + System.out.println(); + } + + private void postorderRecursive(TreeNode node) { + if (node != null) { + postorderRecursive(node.left); + postorderRecursive(node.right); + System.out.print(node.value + " "); } + } + + // ============= HELPER METHODS ============= + public boolean isEmpty() { + return root == null; + } + + public void clear() { + root = null; + } } \ No newline at end of file diff --git a/src/test/java/trees/AVLTreeTest.java b/src/test/java/trees/AVLTreeTest.java index 0b70d1909661..6cfc5aab1233 100644 --- a/src/test/java/trees/AVLTreeTest.java +++ b/src/test/java/trees/AVLTreeTest.java @@ -1,3 +1,4 @@ +// File 5: AVLTreeTest.java package trees; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -6,84 +7,99 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +/** + * Unit tests for AVL Tree implementation + * Reference: https://en.wikipedia.org/wiki/AVL_tree + */ public class AVLTreeTest { - private AVLTree avl; - - @BeforeEach - public void setUp() { - avl = new AVLTree(); - } - - @Test - public void testInsertAndSearch() { - avl.insert(50); - avl.insert(30); - avl.insert(70); - - assertTrue(avl.search(50)); - assertTrue(avl.search(30)); - assertTrue(avl.search(70)); - assertFalse(avl.search(100)); - } - - @Test - public void testBalancing() { - // Insert values in ascending order (would create unbalanced BST) - avl.insert(10); - avl.insert(20); - avl.insert(30); - avl.insert(40); - avl.insert(50); - - // AVL should balance - height should be log(n) - int height = avl.getHeight(); - int n = 5; - int maxHeight = (int) (Math.log(n + 1) / Math.log(2)) + 1; - - assertTrue(height <= maxHeight); - } - - @Test - public void testDelete() { - avl.insert(50); - avl.insert(30); - avl.insert(70); - avl.insert(20); - avl.insert(40); - - avl.delete(20); - assertFalse(avl.search(20)); - - avl.delete(30); - assertFalse(avl.search(30)); - } - - @Test - public void testRotations() { - // Test LL rotation case - AVLTree ll = new AVLTree(); - ll.insert(30); - ll.insert(20); - ll.insert(10); - assertTrue(ll.search(10) && ll.search(20) && ll.search(30)); - - // Test RR rotation case - AVLTree rr = new AVLTree(); - rr.insert(10); - rr.insert(20); - rr.insert(30); - assertTrue(rr.search(10) && rr.search(20) && rr.search(30)); - } - - @Test - public void testFindMinMax() { - avl.insert(50); - avl.insert(30); - avl.insert(70); - avl.insert(20); - avl.insert(80); - - assertEquals(20, avl.findMin()); - assertEquals(80, avl.findMax()); - } + private AVLTree avl; + + @BeforeEach + public void setUp() { + avl = new AVLTree(); + } + + @Test + public void testInsertAndSearch() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + + assertTrue(avl.search(50)); + assertTrue(avl.search(30)); + assertTrue(avl.search(70)); + assertFalse(avl.search(100)); + } + + @Test + public void testBalancing() { + // Insert values in ascending order (would create unbalanced BST) + avl.insert(10); + avl.insert(20); + avl.insert(30); + avl.insert(40); + avl.insert(50); + + // AVL should balance - height should be log(n) + int height = avl.getHeight(); + int n = 5; + int maxHeight = (int) (Math.log(n + 1) / Math.log(2)) + 1; + + assertTrue(height <= maxHeight); + } + + @Test + public void testDelete() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + + avl.delete(20); + assertFalse(avl.search(20)); + + avl.delete(30); + assertFalse(avl.search(30)); + } + + @Test + public void testRotations() { + // Test LL rotation case + AVLTree ll = new AVLTree(); + ll.insert(30); + ll.insert(20); + ll.insert(10); + assertTrue(ll.search(10) && ll.search(20) && ll.search(30)); + + // Test RR rotation case + AVLTree rr = new AVLTree(); + rr.insert(10); + rr.insert(20); + rr.insert(30); + assertTrue(rr.search(10) && rr.search(20) && rr.search(30)); + } + + @Test + public void testFindMinMax() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(80); + + assertEquals(20, avl.findMin()); + assertEquals(80, avl.findMax()); + } + + @Test + public void testIsEmpty() { + assertTrue(avl.isEmpty()); + + avl.insert(50); + assertFalse(avl.isEmpty()); + + avl.clear(); + assertTrue(avl.isEmpty()); + } } \ No newline at end of file diff --git a/src/test/java/trees/BinarySearchTreeTest.java b/src/test/java/trees/BinarySearchTreeTest.java index 0256b148cb54..4c15ff044f18 100644 --- a/src/test/java/trees/BinarySearchTreeTest.java +++ b/src/test/java/trees/BinarySearchTreeTest.java @@ -1,3 +1,4 @@ +// File 4: BinarySearchTreeTest.java package trees; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,68 +8,83 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +/** + * Unit tests for Binary Search Tree implementation + * Reference: https://en.wikipedia.org/wiki/Binary_search_tree + */ public class BinarySearchTreeTest { - private BinarySearchTree bst; - - @BeforeEach - public void setUp() { - bst = new BinarySearchTree(); - } - - @Test - public void testInsertAndSearch() { - bst.insert(50); - bst.insert(30); - bst.insert(70); - - assertTrue(bst.search(50)); - assertTrue(bst.search(30)); - assertTrue(bst.search(70)); - assertFalse(bst.search(100)); - } - - @Test - public void testDuplicateHandling() { - bst.insert(50); - bst.insert(50); - bst.insert(50); - - assertTrue(bst.search(50)); - } - - @Test - public void testDelete() { - bst.insert(50); - bst.insert(30); - bst.insert(70); - bst.insert(20); - bst.insert(40); - - bst.delete(20); // Leaf node - assertFalse(bst.search(20)); - - bst.delete(30); // Node with two children - assertFalse(bst.search(30)); - - bst.delete(50); // Root node - assertFalse(bst.search(50)); - } - - @Test - public void testFindMinMax() { - bst.insert(50); - bst.insert(30); - bst.insert(70); - bst.insert(20); - bst.insert(80); - - assertEquals(20, bst.findMin()); - assertEquals(80, bst.findMax()); - } - - @Test - public void testEmptyTreeException() { - assertThrows(IllegalStateException.class, () -> bst.findMin()); - assertThrows(IllegalStateException.class, () -> bst.findMax()); - } + private BinarySearchTree bst; + + @BeforeEach + public void setUp() { + bst = new BinarySearchTree(); + } + + @Test + public void testInsertAndSearch() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + + assertTrue(bst.search(50)); + assertTrue(bst.search(30)); + assertTrue(bst.search(70)); + assertFalse(bst.search(100)); + } + + @Test + public void testDuplicateHandling() { + bst.insert(50); + bst.insert(50); + bst.insert(50); + + assertTrue(bst.search(50)); + } + + @Test + public void testDelete() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + + bst.delete(20); // Leaf node + assertFalse(bst.search(20)); + + bst.delete(30); // Node with two children + assertFalse(bst.search(30)); + + bst.delete(50); // Root node + assertFalse(bst.search(50)); + } + + @Test + public void testFindMinMax() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(80); + + assertEquals(20, bst.findMin()); + assertEquals(80, bst.findMax()); + } + + @Test + public void testEmptyTreeException() { + assertThrows(IllegalStateException.class, () -> bst.findMin()); + assertThrows(IllegalStateException.class, () -> bst.findMax()); + } + + @Test + public void testIsEmpty() { + assertTrue(bst.isEmpty()); + + bst.insert(50); + assertFalse(bst.isEmpty()); + + bst.clear(); + assertTrue(bst.isEmpty()); + } } \ No newline at end of file