diff --git a/src/main/java/trees/AVLTree.java b/src/main/java/trees/AVLTree.java new file mode 100644 index 000000000000..0d6548c7423c --- /dev/null +++ b/src/main/java/trees/AVLTree.java @@ -0,0 +1,259 @@ +// File 3: AVLTree.java +package trees; + +/** + * AVL Tree Implementation (Self-Balancing Binary Search Tree) + * + * 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; + 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 new file mode 100644 index 000000000000..6abad2bc4da8 --- /dev/null +++ b/src/main/java/trees/BinarySearchTree.java @@ -0,0 +1,183 @@ +// File 2: BinarySearchTree.java +package trees; + +/** + * Binary Search Tree Implementation + * + * 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; + 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); + } + + 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(EMPTY_TREE_ERROR); + } + 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(EMPTY_TREE_ERROR); + } + 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..6cfc5aab1233 --- /dev/null +++ b/src/test/java/trees/AVLTreeTest.java @@ -0,0 +1,105 @@ +// File 5: AVLTreeTest.java +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; + +/** + * 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()); + } + + @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 new file mode 100644 index 000000000000..4c15ff044f18 --- /dev/null +++ b/src/test/java/trees/BinarySearchTreeTest.java @@ -0,0 +1,90 @@ +// File 4: BinarySearchTreeTest.java +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; + +/** + * 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()); + } + + @Test + public void testIsEmpty() { + assertTrue(bst.isEmpty()); + + bst.insert(50); + assertFalse(bst.isEmpty()); + + bst.clear(); + assertTrue(bst.isEmpty()); + } +} \ No newline at end of file