diff --git a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java index 77ee5d5fa23e..d49da467c926 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java @@ -1,269 +1,143 @@ package com.thealgorithms.datastructures.trees; - -import java.util.ArrayList; -import java.util.List; - /** - * Represents an AVL Tree, a self-balancing binary search tree. - * In an AVL tree, the heights of the two child subtrees of any node - * differ by at most one. If they differ by more than one at any time, - * rebalancing is performed to restore this property. + * AVL Tree (Adelson-Velsky and Landis Tree) implementation. + * A self-balancing Binary Search Tree where the difference between heights + * of left and right subtrees cannot be more than one for all nodes. + * + * @author Raghu0703 */ -public class AVLTree { - - private Node root; - - private static class Node { - private int key; - private int balance; - private int height; - private Node left; - private Node right; - private Node parent; +public final class AVLTree { + static class Node { + int data; + int height; + Node left; + Node right; - Node(int k, Node p) { - key = k; - parent = p; - } - - public Integer getBalance() { - return balance; + Node(int data) { + this.data = data; + this.height = 1; + this.left = null; + this.right = null; } } - /** - * Inserts a new key into the AVL tree. - * - * @param key the key to be inserted - * @return {@code true} if the key was inserted, {@code false} if the key already exists - */ - public boolean insert(int key) { - if (root == null) { - root = new Node(key, null); - } else { - Node n = root; - Node parent; - while (true) { - if (n.key == key) { - return false; - } - - parent = n; - boolean goLeft = n.key > key; - n = goLeft ? n.left : n.right; + private Node root; - if (n == null) { - if (goLeft) { - parent.left = new Node(key, parent); - } else { - parent.right = new Node(key, parent); - } - rebalance(parent); - break; - } - } - } - return true; + public AVLTree() { + this.root = null; } - /** - * Deletes a key from the AVL tree. - * - * @param delKey the key to be deleted - */ - public void delete(int delKey) { - if (root == null) { - return; - } - - // Find the node to be deleted - Node node = root; - Node child = root; - while (child != null) { - node = child; - child = delKey >= node.key ? node.right : node.left; - if (delKey == node.key) { - delete(node); - return; - } - } + private int height(Node node) { + return node == null ? 0 : node.height; } - private void delete(Node node) { - if (node.left == null && node.right == null) { - // Leaf node - if (node.parent == null) { - root = null; - } else { - Node parent = node.parent; - if (parent.left == node) { - parent.left = null; - } else { - parent.right = null; - } - rebalance(parent); - } - return; - } - - // Node has one or two children - Node child; - if (node.left != null) { - child = node.left; - while (child.right != null) { - child = child.right; - } - } else { - child = node.right; - while (child.left != null) { - child = child.left; - } - } - node.key = child.key; - delete(child); + private int getBalance(Node node) { + return node == null ? 0 : height(node.left) - height(node.right); } - /** - * Returns a list of balance factors for each node in the tree. - * - * @return a list of integers representing the balance factors of the nodes - */ - public List returnBalance() { - List balances = new ArrayList<>(); - returnBalance(root, balances); - return balances; + private void updateHeight(Node node) { + node.height = Math.max(height(node.left), height(node.right)) + 1; } - private void returnBalance(Node n, List balances) { - if (n != null) { - returnBalance(n.left, balances); - balances.add(n.getBalance()); - returnBalance(n.right, balances); - } + private Node rightRotate(Node y) { + Node x = y.left; + Node t2 = x.right; + x.right = y; + y.left = t2; + updateHeight(y); + updateHeight(x); + return x; } - /** - * Searches for a key in the AVL tree. - * - * @param key the key to be searched - * @return true if the key is found, false otherwise - */ - public boolean search(int key) { - Node result = searchHelper(this.root, key); - return result != null; + private Node leftRotate(Node x) { + Node y = x.right; + Node t2 = y.left; + y.left = x; + x.right = t2; + updateHeight(x); + updateHeight(y); + return y; } - private Node searchHelper(Node root, int key) { - if (root == null || root.key == key) { - return root; - } - - if (root.key > key) { - return searchHelper(root.left, key); - } - return searchHelper(root.right, key); + public void insert(int value) { + root = insertRec(root, value); } - private void rebalance(Node n) { - setBalance(n); - if (n.balance == -2) { - if (height(n.left.left) >= height(n.left.right)) { - n = rotateRight(n); - } else { - n = rotateLeftThenRight(n); - } - } else if (n.balance == 2) { - if (height(n.right.right) >= height(n.right.left)) { - n = rotateLeft(n); - } else { - n = rotateRightThenLeft(n); - } + private Node insertRec(Node node, int value) { + if (node == null) { + return new Node(value); } - - if (n.parent != null) { - rebalance(n.parent); + if (value < node.data) { + node.left = insertRec(node.left, value); + } else if (value > node.data) { + node.right = insertRec(node.right, value); } else { - root = n; + return node; } - } - - private Node rotateLeft(Node a) { - Node b = a.right; - b.parent = a.parent; - - a.right = b.left; - - if (a.right != null) { - a.right.parent = a; + updateHeight(node); + int balance = getBalance(node); + if (balance > 1 && value < node.left.data) { + return rightRotate(node); } - - b.left = a; - a.parent = b; - - if (b.parent != null) { - if (b.parent.right == a) { - b.parent.right = b; - } else { - b.parent.left = b; - } + if (balance < -1 && value > node.right.data) { + return leftRotate(node); } - - setBalance(a, b); - return b; + if (balance > 1 && value > node.left.data) { + node.left = leftRotate(node.left); + return rightRotate(node); + } + if (balance < -1 && value < node.right.data) { + node.right = rightRotate(node.right); + return leftRotate(node); + } + return node; } - private Node rotateRight(Node a) { - Node b = a.left; - b.parent = a.parent; - - a.left = b.right; + public boolean search(int value) { + return searchRec(root, value); + } - if (a.left != null) { - a.left.parent = a; + private boolean searchRec(Node node, int value) { + if (node == null) { + return false; } - - b.right = a; - a.parent = b; - - if (b.parent != null) { - if (b.parent.right == a) { - b.parent.right = b; - } else { - b.parent.left = b; - } + if (value == node.data) { + return true; } + return value < node.data ? searchRec(node.left, value) : searchRec(node.right, value); + } - setBalance(a, b); - return b; + public boolean isEmpty() { + return root == null; } - private Node rotateLeftThenRight(Node n) { - n.left = rotateLeft(n.left); - return rotateRight(n); + public int getHeight() { + return height(root); } - private Node rotateRightThenLeft(Node n) { - n.right = rotateRight(n.right); - return rotateLeft(n); + public boolean isBalanced() { + return isBalancedRec(root); } - private int height(Node n) { - if (n == null) { - return -1; + private boolean isBalancedRec(Node node) { + if (node == null) { + return true; } - return n.height; + int balance = getBalance(node); + return Math.abs(balance) <= 1 && isBalancedRec(node.left) && isBalancedRec(node.right); } - private void setBalance(Node... nodes) { - for (Node n : nodes) { - reheight(n); - n.balance = height(n.right) - height(n.left); - } + public String inorder() { + StringBuilder sb = new StringBuilder(); + inorderRec(root, sb); + return sb.toString().trim(); } - private void reheight(Node node) { + private void inorderRec(Node node, StringBuilder sb) { if (node != null) { - node.height = 1 + Math.max(height(node.left), height(node.right)); + inorderRec(node.left, sb); + sb.append(node.data).append(" "); + inorderRec(node.right, sb); } } } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinarySearchTree.java b/src/main/java/com/thealgorithms/datastructures/trees/BinarySearchTree.java new file mode 100644 index 000000000000..1cc50f98cc8c --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/BinarySearchTree.java @@ -0,0 +1,110 @@ +package com.thealgorithms.datastructures.trees; +/** + * Binary Search Tree implementation + * + * @author Raghu0703 + */ +public final class BinarySearchTree { + static class Node { + int key; + int value; + Node left; + Node right; + + Node(int key, int value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; + } + } + + private Node root; + + public BinarySearchTree() { + this.root = null; + } + + public void insert(int key, int value) { + root = insertRec(root, key, value); + } + + private Node insertRec(Node root, int key, int value) { + if (root == null) { + return new Node(key, value); + } + if (key < root.key) { + root.left = insertRec(root.left, key, value); + } else if (key > root.key) { + root.right = insertRec(root.right, key, value); + } else { + root.value = value; + } + return root; + } + + public Integer search(int key) { + return searchRec(root, key); + } + + private Integer searchRec(Node root, int key) { + if (root == null) { + return null; + } + if (key == root.key) { + return root.value; + } + return key < root.key ? searchRec(root.left, key) : searchRec(root.right, key); + } + + public void delete(int key) { + root = deleteRec(root, key); + } + + private Node deleteRec(Node root, int key) { + if (root == null) { + return null; + } + if (key < root.key) { + root.left = deleteRec(root.left, key); + } else if (key > root.key) { + root.right = deleteRec(root.right, key); + } else { + if (root.left == null) { + return root.right; + } else if (root.right == null) { + return root.left; + } + root.key = minValue(root.right); + root.right = deleteRec(root.right, root.key); + } + return root; + } + + private int minValue(Node root) { + int minv = root.key; + while (root.left != null) { + minv = root.left.key; + root = root.left; + } + return minv; + } + + public boolean isEmpty() { + return root == null; + } + + public String inorder() { + StringBuilder sb = new StringBuilder(); + inorderRec(root, sb); + return sb.toString().trim(); + } + + private void inorderRec(Node root, StringBuilder sb) { + if (root != null) { + inorderRec(root.left, sb); + sb.append(root.key).append(" "); + inorderRec(root.right, sb); + } + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java index 6aa5dc9e22ed..be36f36b06dd 100644 --- a/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java @@ -4,98 +4,361 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class AVLTreeTest { - private AVLTree avlTree; +/** + * Test class for AVLTree + * + * @author Raghu0703 + */ +class AVLTreeTest { + + private AVLTree avl; @BeforeEach - public void setUp() { - avlTree = new AVLTree(); + void setUp() { + avl = new AVLTree(); } @Test - public void testInsert() { - assertTrue(avlTree.insert(10)); - assertTrue(avlTree.insert(20)); - assertTrue(avlTree.insert(5)); - assertFalse(avlTree.insert(10)); // Duplicate + void testEmptyTree() { + assertTrue(avl.isEmpty(), "New tree should be empty"); + assertEquals(0, avl.getHeight(), "Empty tree height should be 0"); + assertTrue(avl.isBalanced(), "Empty tree should be balanced"); } @Test - public void testSearch() { - avlTree.insert(15); - avlTree.insert(25); - assertTrue(avlTree.search(15)); - assertFalse(avlTree.search(30)); // Not in the tree + void testInsertSingleElement() { + avl.insert(10); + assertFalse(avl.isEmpty(), "Tree should not be empty after insert"); + assertTrue(avl.search(10), "Should find inserted element"); + assertEquals(1, avl.getHeight(), "Height should be 1 with single element"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); } @Test - public void testDeleteLeafNode() { - avlTree.insert(10); - avlTree.insert(20); - avlTree.insert(30); - avlTree.delete(30); - assertFalse(avlTree.search(30)); + void testInsertMultipleElements() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + avl.insert(60); + avl.insert(80); + + assertTrue(avl.search(50), "Should find 50"); + assertTrue(avl.search(30), "Should find 30"); + assertTrue(avl.search(70), "Should find 70"); + assertTrue(avl.search(20), "Should find 20"); + assertTrue(avl.search(40), "Should find 40"); + assertTrue(avl.search(60), "Should find 60"); + assertTrue(avl.search(80), "Should find 80"); + assertTrue(avl.isBalanced(), "Tree should remain balanced"); } @Test - public void testDeleteNodeWithOneChild() { - avlTree.insert(20); - avlTree.insert(10); - avlTree.insert(30); - avlTree.delete(10); - assertFalse(avlTree.search(10)); + void testLeftLeftRotation() { + // Insert in descending order to trigger LL rotation + avl.insert(30); + avl.insert(20); + avl.insert(10); + + assertEquals("10 20 30", avl.inorder(), "Tree should be balanced after LL rotation"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + assertEquals(2, avl.getHeight(), "Height should be 2 after balancing"); } @Test - public void testDeleteNodeWithTwoChildren() { - avlTree.insert(20); - avlTree.insert(10); - avlTree.insert(30); - avlTree.insert(25); - avlTree.delete(20); - assertFalse(avlTree.search(20)); - assertTrue(avlTree.search(30)); - assertTrue(avlTree.search(25)); + void testRightRightRotation() { + // Insert in ascending order to trigger RR rotation + avl.insert(10); + avl.insert(20); + avl.insert(30); + + assertEquals("10 20 30", avl.inorder(), "Tree should be balanced after RR rotation"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + assertEquals(2, avl.getHeight(), "Height should be 2 after balancing"); } @Test - public void testReturnBalance() { - avlTree.insert(10); - avlTree.insert(20); - avlTree.insert(5); - List balances = avlTree.returnBalance(); - assertEquals(3, balances.size()); // There should be 3 nodes - assertEquals(0, balances.get(0)); // Balance for node 5 - assertEquals(0, balances.get(1)); // Balance for node 10 - assertEquals(0, balances.get(2)); // Balance for node 20 + void testLeftRightRotation() { + // Insert to trigger LR rotation + avl.insert(30); + avl.insert(10); + avl.insert(20); + + assertEquals("10 20 30", avl.inorder(), "Tree should be balanced after LR rotation"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + assertEquals(2, avl.getHeight(), "Height should be 2 after balancing"); } @Test - public void testInsertAndRebalance() { - avlTree.insert(30); - avlTree.insert(20); - avlTree.insert(10); // This should cause a right rotation - assertTrue(avlTree.search(20)); - assertTrue(avlTree.search(10)); - assertTrue(avlTree.search(30)); + void testRightLeftRotation() { + // Insert to trigger RL rotation + avl.insert(10); + avl.insert(30); + avl.insert(20); + + assertEquals("10 20 30", avl.inorder(), "Tree should be balanced after RL rotation"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + assertEquals(2, avl.getHeight(), "Height should be 2 after balancing"); + } + + @Test + void testSearchNonExistentElement() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + + assertFalse(avl.search(100), "Should not find non-existent element"); + assertFalse(avl.search(25), "Should not find non-existent element"); + } + + @Test + void testSearchInEmptyTree() { + assertFalse(avl.search(10), "Should not find element in empty tree"); + } + + @Test + void testInorderTraversal() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + avl.insert(60); + avl.insert(80); + + String expected = "20 30 40 50 60 70 80"; + assertEquals(expected, avl.inorder(), "Inorder traversal should be sorted"); + } + + @Test + void testPreorderTraversal() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + + String result = avl.preorder(); + assertTrue(result.contains("50") && result.contains("30") && result.contains("70"), "Preorder traversal should contain all elements"); } @Test - public void testComplexInsertionAndDeletion() { - avlTree.insert(30); - avlTree.insert(20); - avlTree.insert(10); - avlTree.insert(25); - avlTree.insert(5); - avlTree.insert(15); + void testDeleteLeafNode() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + + avl.delete(20); + assertFalse(avl.search(20), "Deleted leaf node should not be found"); + assertTrue(avl.isBalanced(), "Tree should remain balanced after deletion"); + assertEquals("30 50 70", avl.inorder(), "Tree should be correct after deletion"); + } + + @Test + void testDeleteNodeWithOneChild() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(60); + + avl.delete(70); + assertFalse(avl.search(70), "Deleted node should not be found"); + assertTrue(avl.search(60), "Child of deleted node should still exist"); + assertTrue(avl.isBalanced(), "Tree should remain balanced after deletion"); + } + + @Test + void testDeleteNodeWithTwoChildren() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + avl.insert(60); + avl.insert(80); + + avl.delete(50); + assertFalse(avl.search(50), "Deleted node should not be found"); + assertTrue(avl.isBalanced(), "Tree should remain balanced after deletion"); + String result = avl.inorder(); + assertEquals("20 30 40 60 70 80", result, "Tree should be correct after deletion"); + } + + @Test + void testDeleteRootNode() { + avl.insert(50); + avl.delete(50); + assertTrue(avl.isEmpty(), "Tree should be empty after deleting only node"); + } + + @Test + void testDeleteNonExistentElement() { + avl.insert(50); + avl.insert(30); + avl.insert(70); + + avl.delete(100); + assertEquals("30 50 70", avl.inorder(), "Tree should remain unchanged"); + assertTrue(avl.isBalanced(), "Tree should remain balanced"); + } + + @Test + void testDeleteWithRebalancing() { + // Create a tree that requires rebalancing after deletion + avl.insert(50); + avl.insert(25); + avl.insert(75); + avl.insert(10); + avl.insert(30); + avl.insert(60); + avl.insert(80); + avl.insert(5); + avl.insert(15); + + // Delete node that will trigger rebalancing + avl.delete(80); + avl.delete(75); + + assertTrue(avl.isBalanced(), "Tree should be balanced after deletions"); + assertFalse(avl.search(80), "Deleted element should not be found"); + assertFalse(avl.search(75), "Deleted element should not be found"); + } + + @Test + void testHeight() { + avl.insert(50); + assertEquals(1, avl.getHeight(), "Height should be 1"); + + avl.insert(30); + avl.insert(70); + assertEquals(2, avl.getHeight(), "Height should be 2"); + + avl.insert(20); + avl.insert(40); + avl.insert(60); + avl.insert(80); + // AVL tree should keep height minimal + assertTrue(avl.getHeight() <= 4, "Height should be logarithmic"); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + } + + @Test + void testInsertDuplicates() { + avl.insert(50); + avl.insert(30); + avl.insert(50); // Duplicate + + String result = avl.inorder(); + assertTrue(result.contains("30") && result.contains("50"), "Tree should contain all unique values"); + assertTrue(avl.isBalanced(), "Tree should remain balanced"); + } + + @Test + void testComplexOperations() { + // Insert elements + avl.insert(50); + avl.insert(30); + avl.insert(70); + avl.insert(20); + avl.insert(40); + avl.insert(60); + avl.insert(80); + + // Verify structure + assertEquals("20 30 40 50 60 70 80", avl.inorder()); + assertTrue(avl.isBalanced(), "Tree should be balanced"); + + // Delete some elements + avl.delete(20); + avl.delete(70); + + // Verify after deletions + assertEquals("30 40 50 60 80", avl.inorder()); + assertTrue(avl.isBalanced(), "Tree should remain balanced after deletions"); + + // Search for remaining elements + assertTrue(avl.search(50)); + assertTrue(avl.search(30)); + assertFalse(avl.search(20)); + assertFalse(avl.search(70)); + } + + @Test + void testAscendingOrder() { + // Insert in ascending order - AVL should handle this efficiently + for (int i = 1; i <= 10; i++) { + avl.insert(i); + } + + assertEquals("1 2 3 4 5 6 7 8 9 10", avl.inorder()); + assertTrue(avl.isBalanced(), "Tree should remain balanced with sequential inserts"); + // Height should be logarithmic, not linear + assertTrue(avl.getHeight() <= 5, "Height should be logarithmic for 10 elements"); + } + + @Test + void testDescendingOrder() { + // Insert in descending order - AVL should handle this efficiently + for (int i = 10; i >= 1; i--) { + avl.insert(i); + } + + assertEquals("1 2 3 4 5 6 7 8 9 10", avl.inorder()); + assertTrue(avl.isBalanced(), "Tree should remain balanced with reverse sequential inserts"); + assertTrue(avl.getHeight() <= 5, "Height should be logarithmic for 10 elements"); + } + + @Test + void testLargeNumberOfElements() { + // Insert many elements to test balancing efficiency + for (int i = 1; i <= 100; i++) { + avl.insert(i); + assertTrue(avl.isBalanced(), "Tree should remain balanced at every insertion"); + } + + // AVL tree with 100 elements should have height around log2(100) ≈ 7 + assertTrue(avl.getHeight() <= 10, "Height should be logarithmic for 100 elements"); + + // Verify all elements are searchable + for (int i = 1; i <= 100; i++) { + assertTrue(avl.search(i), "Should find element " + i); + } + } + + @Test + void testBalanceAfterMultipleDeletions() { + // Insert elements + for (int i = 1; i <= 15; i++) { + avl.insert(i); + } + + // Delete half of them + for (int i = 1; i <= 15; i += 2) { + avl.delete(i); + } + + assertTrue(avl.isBalanced(), "Tree should remain balanced after multiple deletions"); + + // Verify remaining elements + for (int i = 2; i <= 14; i += 2) { + assertTrue(avl.search(i), "Should find remaining element " + i); + } + } + + @Test + void testEmptyTreeAfterDeletions() { + avl.insert(10); + avl.insert(20); + avl.insert(30); + + avl.delete(10); + avl.delete(20); + avl.delete(30); - avlTree.delete(20); // Test deletion - assertFalse(avlTree.search(20)); - assertTrue(avlTree.search(30)); - assertTrue(avlTree.search(25)); + assertTrue(avl.isEmpty(), "Tree should be empty after all deletions"); + assertEquals(0, avl.getHeight(), "Height should be 0 for empty tree"); } } diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BinarySearchTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinarySearchTreeTest.java new file mode 100644 index 000000000000..44d2fe4d0c20 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BinarySearchTreeTest.java @@ -0,0 +1,245 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test class for BinarySearchTree + * + * @author Raghu0703 + */ +class BinarySearchTreeTest { + + private BinarySearchTree bst; + + @BeforeEach + void setUp() { + bst = new BinarySearchTree(); + } + + @Test + void testEmptyTree() { + assertTrue(bst.isEmpty(), "New tree should be empty"); + assertEquals(0, bst.height(), "Empty tree height should be 0"); + } + + @Test + void testInsertSingleElement() { + bst.insert(10); + assertFalse(bst.isEmpty(), "Tree should not be empty after insert"); + assertTrue(bst.search(10), "Should find inserted element"); + assertEquals(1, bst.height(), "Height should be 1 with single element"); + } + + @Test + void testInsertMultipleElements() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + bst.insert(60); + bst.insert(80); + + assertTrue(bst.search(50), "Should find 50"); + assertTrue(bst.search(30), "Should find 30"); + assertTrue(bst.search(70), "Should find 70"); + assertTrue(bst.search(20), "Should find 20"); + assertTrue(bst.search(40), "Should find 40"); + assertTrue(bst.search(60), "Should find 60"); + assertTrue(bst.search(80), "Should find 80"); + } + + @Test + void testSearchNonExistentElement() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + + assertFalse(bst.search(100), "Should not find non-existent element"); + assertFalse(bst.search(25), "Should not find non-existent element"); + } + + @Test + void testSearchInEmptyTree() { + assertFalse(bst.search(10), "Should not find element in empty tree"); + } + + @Test + void testInorderTraversal() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + bst.insert(60); + bst.insert(80); + + String expected = "20 30 40 50 60 70 80"; + assertEquals(expected, bst.inorder(), "Inorder traversal should be sorted"); + } + + @Test + void testPreorderTraversal() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + + String expected = "50 30 20 40 70"; + assertEquals(expected, bst.preorder(), "Preorder traversal should match"); + } + + @Test + void testPostorderTraversal() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + + String expected = "20 40 30 70 50"; + assertEquals(expected, bst.postorder(), "Postorder traversal should match"); + } + + @Test + void testDeleteLeafNode() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + + bst.delete(20); + assertFalse(bst.search(20), "Deleted leaf node should not be found"); + assertEquals("30 50 70", bst.inorder(), "Tree should be correct after deletion"); + } + + @Test + void testDeleteNodeWithOneChild() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(60); + + bst.delete(70); + assertFalse(bst.search(70), "Deleted node should not be found"); + assertTrue(bst.search(60), "Child of deleted node should still exist"); + assertEquals("30 50 60", bst.inorder(), "Tree should be correct after deletion"); + } + + @Test + void testDeleteNodeWithTwoChildren() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + bst.insert(60); + bst.insert(80); + + bst.delete(50); + assertFalse(bst.search(50), "Deleted node should not be found"); + assertEquals("20 30 40 60 70 80", bst.inorder(), "Tree should be correct after deletion"); + } + + @Test + void testDeleteRootNode() { + bst.insert(50); + bst.delete(50); + assertTrue(bst.isEmpty(), "Tree should be empty after deleting only node"); + } + + @Test + void testDeleteNonExistentElement() { + bst.insert(50); + bst.insert(30); + bst.insert(70); + + bst.delete(100); + assertEquals("30 50 70", bst.inorder(), "Tree should remain unchanged"); + } + + @Test + void testHeight() { + bst.insert(50); + assertEquals(1, bst.height(), "Height should be 1"); + + bst.insert(30); + bst.insert(70); + assertEquals(2, bst.height(), "Height should be 2"); + + bst.insert(20); + assertEquals(3, bst.height(), "Height should be 3"); + } + + @Test + void testInsertDuplicates() { + bst.insert(50); + bst.insert(30); + bst.insert(50); // Duplicate + + String result = bst.inorder(); + // BST should handle duplicates by either ignoring or placing right + assertTrue(result.contains("30") && result.contains("50"), "Tree should contain all unique values"); + } + + @Test + void testComplexOperations() { + // Insert elements + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + bst.insert(60); + bst.insert(80); + + // Verify structure + assertEquals("20 30 40 50 60 70 80", bst.inorder()); + + // Delete some elements + bst.delete(20); + bst.delete(70); + + // Verify after deletions + assertEquals("30 40 50 60 80", bst.inorder()); + + // Search for remaining elements + assertTrue(bst.search(50)); + assertTrue(bst.search(30)); + assertFalse(bst.search(20)); + assertFalse(bst.search(70)); + } + + @Test + void testAscendingOrder() { + // Insert in ascending order + bst.insert(10); + bst.insert(20); + bst.insert(30); + bst.insert(40); + bst.insert(50); + + assertEquals("10 20 30 40 50", bst.inorder()); + assertTrue(bst.search(30)); + } + + @Test + void testDescendingOrder() { + // Insert in descending order + bst.insert(50); + bst.insert(40); + bst.insert(30); + bst.insert(20); + bst.insert(10); + + assertEquals("10 20 30 40 50", bst.inorder()); + assertTrue(bst.search(30)); + } +}