diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 000000000000..3dfcbb05764b --- /dev/null +++ b/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +LocalizedResourceName=@Java,0 diff --git a/pmd-custom_ruleset.xml b/pmd-custom_ruleset.xml index 19bb1c7968f0..70c6bc7872e8 100644 --- a/pmd-custom_ruleset.xml +++ b/pmd-custom_ruleset.xml @@ -8,7 +8,7 @@ Custom PMD checks for TheAlgorithms/Java diff --git a/src/main/java/com/thealgorithms/tree/AVLTree.java b/src/main/java/com/thealgorithms/tree/AVLTree.java new file mode 100644 index 000000000000..eb72ada818b8 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/AVLTree.java @@ -0,0 +1,207 @@ +package com.thealgorithms.tree; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Generic AVL Tree (self-balancing BST). Duplicates are handled via a count in each node. + * + * @param key type (must be Comparable) + */ +public class AVLTree> { + + static class Node { + T key; + Node left, right; + int height; + int count; + + Node(T key) { + this.key = key; + this.height = 1; + this.count = 1; + } + } + + private Node root; + private int size; // distinct nodes + + public AVLTree() {} + + // Public API + public void insert(T key) { + Objects.requireNonNull(key); + root = insert(root, key); + } + + public boolean contains(T key) { + Objects.requireNonNull(key); + Node cur = root; + while (cur != null) { + int cmp = key.compareTo(cur.key); + if (cmp == 0) return true; + cur = (cmp < 0) ? cur.left : cur.right; + } + return false; + } + + public boolean delete(T key) { + Objects.requireNonNull(key); + if (!contains(key)) return false; + root = delete(root, key); + return true; + } + + public T findMin() { + if (root == null) return null; + Node cur = root; + while (cur.left != null) cur = cur.left; + return cur.key; + } + + public T findMax() { + if (root == null) return null; + Node cur = root; + while (cur.right != null) cur = cur.right; + return cur.key; + } + + public List inorder() { + List out = new ArrayList<>(); + inorder(root, out); + return out; + } + + public int sizeDistinct() { + return size; + } + + public int countOccurrences(T key) { + Node cur = root; + while (cur != null) { + int cmp = key.compareTo(cur.key); + if (cmp == 0) return cur.count; + cur = (cmp < 0) ? cur.left : cur.right; + } + return 0; + } + + // === Private helpers === + private Node insert(Node node, T key) { + if (node == null) { + size++; + return new Node<>(key); + } + int cmp = key.compareTo(node.key); + if (cmp < 0) { + node.left = insert(node.left, key); + } else if (cmp > 0) { + node.right = insert(node.right, key); + } else { + node.count++; + return node; + } + + updateHeight(node); + return balance(node); + } + + private Node delete(Node node, T key) { + if (node == null) return null; + int cmp = key.compareTo(node.key); + if (cmp < 0) { + node.left = delete(node.left, key); + } else if (cmp > 0) { + node.right = delete(node.right, key); + } else { + if (node.count > 1) { + node.count--; + return node; + } + // remove node + size--; + if (node.left == null) return node.right; + if (node.right == null) return node.left; + // two children: replace with successor + Node succ = minNode(node.right); + node.key = succ.key; + node.count = succ.count; + node.right = deleteNodeMin(node.right); + } + updateHeight(node); + return balance(node); + } + + private Node minNode(Node node) { + while (node.left != null) node = node.left; + return node; + } + + private Node deleteNodeMin(Node node) { + if (node.left == null) return node.right; + node.left = deleteNodeMin(node.left); + updateHeight(node); + return balance(node); + } + + private void inorder(Node node, List out) { + if (node == null) return; + inorder(node.left, out); + for (int i = 0; i < node.count; i++) out.add(node.key); + inorder(node.right, out); + } + + // --- AVL utilities --- + private int height(Node n) { + return n == null ? 0 : n.height; + } + + private void updateHeight(Node n) { + n.height = 1 + Math.max(height(n.left), height(n.right)); + } + + private int balanceFactor(Node n) { + return (n == null) ? 0 : height(n.left) - height(n.right); + } + + private Node balance(Node n) { + int bf = balanceFactor(n); + if (bf > 1) { + if (balanceFactor(n.left) < 0) { + // Left-Right + n.left = rotateLeft(n.left); + } + // Left-Left + return rotateRight(n); + } else if (bf < -1) { + if (balanceFactor(n.right) > 0) { + // Right-Left + n.right = rotateRight(n.right); + } + // Right-Right + return rotateLeft(n); + } + return n; + } + + private Node rotateRight(Node y) { + Node x = y.left; + Node T2 = x.right; + x.right = y; + y.left = T2; + updateHeight(y); + updateHeight(x); + return x; + } + + private Node rotateLeft(Node x) { + Node y = x.right; + Node T2 = y.left; + y.left = x; + x.right = T2; + updateHeight(x); + updateHeight(y); + return y; + } +} diff --git a/src/main/java/com/thealgorithms/tree/BinarySearchTree.java b/src/main/java/com/thealgorithms/tree/BinarySearchTree.java new file mode 100644 index 000000000000..19b0e03e66f4 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/BinarySearchTree.java @@ -0,0 +1,176 @@ +package com.thealgorithms.tree; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Generic Binary Search Tree that allows duplicate keys via a counter in each node. + * + * @param key type (must be Comparable) + */ +public class BinarySearchTree> { + + static class Node { + T key; + Node left, right; + int count; // number of duplicates (>=1) + + Node(T key) { + this.key = key; + this.count = 1; + } + } + + private Node root; + private int size; // number of distinct nodes (not counting duplicates) + + public BinarySearchTree() {} + + // Public API + public void insert(T key) { + Objects.requireNonNull(key); + root = insert(root, key); + } + + public boolean contains(T key) { + Objects.requireNonNull(key); + Node cur = root; + while (cur != null) { + int cmp = key.compareTo(cur.key); + if (cmp == 0) return true; + cur = (cmp < 0) ? cur.left : cur.right; + } + return false; + } + + public boolean delete(T key) { + Objects.requireNonNull(key); + int initialCount = countOccurrences(key); + if (initialCount == 0) return false; + root = delete(root, key); + return countOccurrences(key) < initialCount; + } + + public T findMin() { + if (root == null) return null; + Node cur = root; + while (cur.left != null) cur = cur.left; + return cur.key; + } + + public T findMax() { + if (root == null) return null; + Node cur = root; + while (cur.right != null) cur = cur.right; + return cur.key; + } + + public List inorder() { + List out = new ArrayList<>(); + inorder(root, out); + return out; + } + + public List preorder() { + List out = new ArrayList<>(); + preorder(root, out); + return out; + } + + public List postorder() { + List out = new ArrayList<>(); + postorder(root, out); + return out; + } + + public int sizeDistinct() { + return size; + } + + public int countOccurrences(T key) { + Node cur = root; + while (cur != null) { + int cmp = key.compareTo(cur.key); + if (cmp == 0) return cur.count; + cur = (cmp < 0) ? cur.left : cur.right; + } + return 0; + } + + // === Private helpers === + private Node insert(Node node, T key) { + if (node == null) { + size++; + return new Node<>(key); + } + int cmp = key.compareTo(node.key); + if (cmp < 0) { + node.left = insert(node.left, key); + } else if (cmp > 0) { + node.right = insert(node.right, key); + } else { + node.count++; + } + return node; + } + + private Node delete(Node node, T key) { + if (node == null) return null; + int cmp = key.compareTo(node.key); + if (cmp < 0) { + node.left = delete(node.left, key); + } else if (cmp > 0) { + node.right = delete(node.right, key); + } else { + // found + if (node.count > 1) { + node.count--; + return node; + } + // remove node + size--; + if (node.left == null) return node.right; + if (node.right == null) return node.left; + // two children: find successor + Node successor = minNode(node.right); + node.key = successor.key; + node.count = successor.count; + // delete successor node (all its counts) + node.right = deleteNodeMin(node.right); + } + return node; + } + + private Node minNode(Node node) { + while (node.left != null) node = node.left; + return node; + } + + private Node deleteNodeMin(Node node) { + if (node.left == null) return node.right; + node.left = deleteNodeMin(node.left); + return node; + } + + private void inorder(Node node, List out) { + if (node == null) return; + inorder(node.left, out); + for (int i = 0; i < node.count; i++) out.add(node.key); + inorder(node.right, out); + } + + private void preorder(Node node, List out) { + if (node == null) return; + for (int i = 0; i < node.count; i++) out.add(node.key); + preorder(node.left, out); + preorder(node.right, out); + } + + private void postorder(Node node, List out) { + if (node == null) return; + postorder(node.left, out); + postorder(node.right, out); + for (int i = 0; i < node.count; i++) out.add(node.key); + } +} diff --git a/src/main/java/com/thealgorithms/tree/Main.java b/src/main/java/com/thealgorithms/tree/Main.java new file mode 100644 index 000000000000..4bc71e7753fb --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/Main.java @@ -0,0 +1,40 @@ +package com.thealgorithms.tree; + +public class Main { + public static void main(String[] args) { + // --- Test BST --- + BinarySearchTree bst = new BinarySearchTree<>(); + bst.insert(50); + bst.insert(30); + bst.insert(70); + bst.insert(20); + bst.insert(40); + bst.insert(60); + bst.insert(80); + + System.out.println("BST Inorder: " + bst.inorder()); + System.out.println("Min: " + bst.findMin()); + System.out.println("Max: " + bst.findMax()); + System.out.println("Contains 40? " + bst.contains(40)); + + bst.delete(30); + System.out.println("After deleting 30: " + bst.inorder()); + + // --- Test AVL Tree --- + AVLTree avl = new AVLTree<>(); + avl.insert(10); + avl.insert(20); + avl.insert(30); + avl.insert(40); + avl.insert(50); + avl.insert(25); + + System.out.println("\nAVL Inorder: " + avl.inorder()); + System.out.println("Min: " + avl.findMin()); + System.out.println("Max: " + avl.findMax()); + System.out.println("Contains 25? " + avl.contains(25)); + + avl.delete(30); + System.out.println("After deleting 30: " + avl.inorder()); + } +} diff --git a/src/test/java/com/thealgorithms/tree/AVLTreeTest.java b/src/test/java/com/thealgorithms/tree/AVLTreeTest.java new file mode 100644 index 000000000000..4ea3fa21ee24 --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/AVLTreeTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.tree; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +public class AVLTreeTest { + + @Test + void insertAndBalance() { + AVLTree avl = new AVLTree<>(); + avl.insert(30); + avl.insert(20); + avl.insert(10); // triggers right rotation (LL) + + assertTrue(avl.contains(20)); + assertEquals(10, avl.findMin()); + assertEquals(30, avl.findMax()); + + List in = avl.inorder(); + assertArrayEquals(new Integer[]{10,20,30}, in.toArray(new Integer[0])); + } + + @Test + void duplicatesAndDelete() { + AVLTree avl = new AVLTree<>(); + avl.insert(5); + avl.insert(5); + assertEquals(2, avl.countOccurrences(5)); + avl.delete(5); + assertEquals(1, avl.countOccurrences(5)); + avl.delete(5); + assertFalse(avl.contains(5)); + } +} diff --git a/src/test/java/com/thealgorithms/tree/BinarySearchTreeTest.java b/src/test/java/com/thealgorithms/tree/BinarySearchTreeTest.java new file mode 100644 index 000000000000..770c6e129bfd --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/BinarySearchTreeTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.tree; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +public class BinarySearchTreeTest { + + @Test + void basicOperations() { + BinarySearchTree bst = new BinarySearchTree<>(); + bst.insert(5); + bst.insert(3); + bst.insert(7); + bst.insert(3); // duplicate + + assertTrue(bst.contains(5)); + assertTrue(bst.contains(3)); + assertEquals(2, bst.countOccurrences(3)); + + List inorder = bst.inorder(); + // inorder should be [3,3,5,7] + assertArrayEquals(new Integer[]{3,3,5,7}, inorder.toArray(new Integer[0])); + + assertEquals(3, bst.findMin()); + assertEquals(7, bst.findMax()); + + assertTrue(bst.delete(3)); // decrement duplicate + assertEquals(1, bst.countOccurrences(3)); + assertTrue(bst.delete(3)); // remove node entirely + assertFalse(bst.contains(3)); + } +}