diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/balancedbinarytree/AVLTree.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/balancedbinarytree/AVLTree.java new file mode 100644 index 0000000000..a0575c8be1 --- /dev/null +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/balancedbinarytree/AVLTree.java @@ -0,0 +1,141 @@ +package com.baeldung.algorithms.balancedbinarytree; + +public class AVLTree { + + public class Node { + int key; + int height; + Node left; + Node right; + + Node(int key) { + this.key = key; + } + } + + private Node root; + + public Node find(int key) { + Node current = root; + while (current != null) { + if (current.key == key) { + break; + } + current = current.key < key ? current.right : current.left; + } + return current; + } + + public void insert(int key) { + root = insert(root, key); + } + + public void delete(int key) { + root = delete(root, key); + } + + public Node getRoot() { + return root; + } + + public int height() { + return root == null ? -1 : root.height; + } + + private Node insert(Node node, int key) { + if (node == null) { + return new Node(key); + } else if (node.key > key) { + node.left = insert(node.left, key); + } else if (node.key < key) { + node.right = insert(node.right, key); + } else { + throw new RuntimeException("duplicate Key!"); + } + return rebalance(node); + } + + private Node delete(Node node, int key) { + if (node == null) { + return node; + } else if (node.key > key) { + node.left = delete(node.left, key); + } else if (node.key < key) { + node.right = delete(node.right, key); + } else { + if (node.left == null || node.right == null) { + node = (node.left == null) ? node.right : node.left; + } else { + Node mostLeftChild = mostLeftChild(node.right); + node.key = mostLeftChild.key; + node.right = delete(node.right, node.key); + } + } + if (node != null) { + node = rebalance(node); + } + return node; + } + + private Node mostLeftChild(Node node) { + Node current = node; + /* loop down to find the leftmost leaf */ + while (current.left != null) { + current = current.left; + } + return current; + } + + private Node rebalance(Node z) { + updateHeight(z); + int balance = getBalance(z); + if (balance > 1) { + if (height(z.right.right) > height(z.right.left)) { + z = rotateLeft(z); + } else { + z.right = rotateRight(z.right); + z = rotateLeft(z); + } + } else if (balance < -1) { + if (height(z.left.left) > height(z.left.right)) { + z = rotateRight(z); + } else { + z.left = rotateLeft(z.left); + z = rotateRight(z); + } + } + return z; + } + + private Node rotateRight(Node y) { + Node x = y.left; + Node z = x.right; + x.right = y; + y.left = z; + updateHeight(y); + updateHeight(x); + return x; + } + + private Node rotateLeft(Node y) { + Node x = y.right; + Node z = x.left; + x.left = y; + y.right = z; + updateHeight(y); + updateHeight(x); + return x; + } + + private void updateHeight(Node n) { + n.height = 1 + Math.max(height(n.left), height(n.right)); + } + + private int height(Node n) { + return n == null ? -1 : n.height; + } + + public int getBalance(Node n) { + return (n == null) ? 0 : height(n.right) - height(n.left); + } +} diff --git a/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/balancedbinarytree/AVLTreeUnitTest.java b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/balancedbinarytree/AVLTreeUnitTest.java new file mode 100644 index 0000000000..ab001b6696 --- /dev/null +++ b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/balancedbinarytree/AVLTreeUnitTest.java @@ -0,0 +1,83 @@ +package com.baeldung.algorithms.balancedbinarytree; + +import org.junit.Assert; +import org.junit.Test; + +public class AVLTreeUnitTest { + + @Test + public void givenEmptyTree_whenHeightCalled_shouldReturnMinus1() { + AVLTree tree = new AVLTree(); + Assert.assertEquals(-1, tree.height()); + } + + @Test + public void givenEmptyTree_whenInsertCalled_heightShouldBeZero() { + AVLTree tree = new AVLTree(); + tree.insert(1); + Assert.assertEquals(0, tree.height()); + } + + @Test + public void givenEmptyTree_whenInsertCalled_treeShouldBeAvl() { + AVLTree tree = new AVLTree(); + tree.insert(1); + Assert.assertTrue(isAVL(tree)); + } + + @Test + public void givenSampleTree_whenInsertCalled_treeShouldBeAvl() { + AVLTree tree = getSampleAVLTree(); + int newKey = 11; + tree.insert(newKey); + Assert.assertTrue(isAVL(tree)); + } + + @Test + public void givenSampleTree_whenFindExistingKeyCalled_shouldReturnMatchedNode() { + AVLTree tree = getSampleAVLTree(); + int existingKey = 2; + AVLTree.Node result = tree.find(existingKey); + Assert.assertEquals(result.key, existingKey); + } + + @Test + public void givenSampleTree_whenFindNotExistingKeyCalled_shouldReturnNull() { + AVLTree tree = getSampleAVLTree(); + int notExistingKey = 11; + AVLTree.Node result = tree.find(notExistingKey); + Assert.assertNull(result); + } + + @Test + public void givenEmptyTree_whenDeleteCalled_treeShouldBeAvl() { + AVLTree tree = new AVLTree(); + tree.delete(1); + Assert.assertTrue(isAVL(tree)); + } + + @Test + public void givenSampleTree_whenDeleteCalled_treeShouldBeAvl() { + AVLTree tree = getSampleAVLTree(); + tree.delete(1); + Assert.assertTrue(isAVL(tree, tree.getRoot())); + } + + private boolean isAVL(AVLTree tree) { + return isAVL(tree, tree.getRoot()); + } + + private boolean isAVL(AVLTree tree, AVLTree.Node node) { + if ( node == null ) + return true; + int balance = tree.getBalance(node); + return (balance <= 1 && balance >= -1) && isAVL(tree, node.left) && isAVL(tree, node.right); + } + + private AVLTree getSampleAVLTree() { + AVLTree avlTree = new AVLTree(); + for (int i = 0; i < 10; i++) + avlTree.insert(i); + return avlTree; + } +}