package com.jwetherell.algorithms.data_structures; import java.util.ArrayList; import java.util.List; /** * An AVL tree is a self-balancing binary search tree, and it was the first such * data structure to be invented. In an AVL tree, the heights of the two child * subtrees of any node differ by at most one. AVL trees are often compared with * red-black trees because they support the same set of operations and because * red-black trees also take O(log n) time for the basic operations. Because AVL * trees are more rigidly balanced, they are faster than red-black trees for * lookup intensive applications. However, red-black trees are faster for * insertion and removal. * * http://en.wikipedia.org/wiki/AVL_tree * * @author Justin Wetherell */ public class AVLTree> extends BinarySearchTree { private enum Balance { LEFT_LEFT, LEFT_RIGHT, RIGHT_LEFT, RIGHT_RIGHT } /** * Default constructor. */ public AVLTree() { this.creator = new BinarySearchTree.INodeCreator() { /** * {@inheritDoc} */ @Override public BinarySearchTree.Node createNewNode(BinarySearchTree.Node parent, T id) { return (new AVLNode(parent, id)); } }; } /** * Constructor with external Node creator. */ public AVLTree(INodeCreator creator) { super(creator); } /** * {@inheritDoc} */ @Override protected Node addValue(T id) { Node nodeToReturn = super.addValue(id); AVLNode nodeAdded = (AVLNode) nodeToReturn; nodeAdded.updateHeight(); balanceAfterInsert(nodeAdded); nodeAdded = (AVLNode) nodeAdded.parent; while (nodeAdded != null) { int h1 = nodeAdded.height; nodeAdded.updateHeight(); balanceAfterInsert(nodeAdded); // If height before and after balance is the same, stop going up the tree int h2 = nodeAdded.height; if (h1==h2) break; nodeAdded = (AVLNode) nodeAdded.parent; } return nodeToReturn; } /** * Balance the tree according to the AVL post-insert algorithm. * * @param node * Root of tree to balance. */ private void balanceAfterInsert(AVLNode node) { int balanceFactor = node.getBalanceFactor(); if (balanceFactor > 1 || balanceFactor < -1) { AVLNode child = null; Balance balance = null; if (balanceFactor < 0) { child = (AVLNode) node.lesser; balanceFactor = child.getBalanceFactor(); if (balanceFactor < 0) balance = Balance.LEFT_LEFT; else balance = Balance.LEFT_RIGHT; } else { child = (AVLNode) node.greater; balanceFactor = child.getBalanceFactor(); if (balanceFactor < 0) balance = Balance.RIGHT_LEFT; else balance = Balance.RIGHT_RIGHT; } if (balance == Balance.LEFT_RIGHT) { // Left-Right (Left rotation, right rotation) rotateLeft(child); rotateRight(node); } else if (balance == Balance.RIGHT_LEFT) { // Right-Left (Right rotation, left rotation) rotateRight(child); rotateLeft(node); } else if (balance == Balance.LEFT_LEFT) { // Left-Left (Right rotation) rotateRight(node); } else { // Right-Right (Left rotation) rotateLeft(node); } child.updateHeight(); node.updateHeight(); } } /** * {@inheritDoc} */ @Override protected Node removeValue(T value) { // Find node to remove Node nodeToRemoved = this.getNode(value); if (nodeToRemoved==null) return null; // Find the replacement node Node replacementNode = this.getReplacementNode(nodeToRemoved); // Find the parent of the replacement node to re-factor the height/balance of the tree AVLNode nodeToRefactor = null; if (replacementNode != null) nodeToRefactor = (AVLNode) replacementNode.parent; if (nodeToRefactor == null) nodeToRefactor = (AVLNode) nodeToRemoved.parent; if (nodeToRefactor != null && nodeToRefactor == nodeToRemoved) nodeToRefactor = (AVLNode) replacementNode; // Replace the node replaceNodeWithNode(nodeToRemoved, replacementNode); // Re-balance the tree all the way up the tree while (nodeToRefactor != null) { nodeToRefactor.updateHeight(); balanceAfterDelete(nodeToRefactor); nodeToRefactor = (AVLNode) nodeToRefactor.parent; } return nodeToRemoved; } /** * Balance the tree according to the AVL post-delete algorithm. * * @param node * Root of tree to balance. */ private void balanceAfterDelete(AVLNode node) { int balanceFactor = node.getBalanceFactor(); if (balanceFactor == -2 || balanceFactor == 2) { if (balanceFactor == -2) { AVLNode ll = (AVLNode) node.lesser.lesser; int lesser = (ll != null) ? ll.height : 0; AVLNode lr = (AVLNode) node.lesser.greater; int greater = (lr != null) ? lr.height : 0; if (lesser >= greater) { rotateRight(node); node.updateHeight(); if (node.parent != null) ((AVLNode) node.parent).updateHeight(); } else { rotateLeft(node.lesser); rotateRight(node); AVLNode p = (AVLNode) node.parent; if (p.lesser != null) ((AVLNode) p.lesser).updateHeight(); if (p.greater != null) ((AVLNode) p.greater).updateHeight(); p.updateHeight(); } } else if (balanceFactor == 2) { AVLNode rr = (AVLNode) node.greater.greater; int greater = (rr != null) ? rr.height : 0; AVLNode rl = (AVLNode) node.greater.lesser; int lesser = (rl != null) ? rl.height : 0; if (greater >= lesser) { rotateLeft(node); node.updateHeight(); if (node.parent != null) ((AVLNode) node.parent).updateHeight(); } else { rotateRight(node.greater); rotateLeft(node); AVLNode p = (AVLNode) node.parent; if (p.lesser != null) ((AVLNode) p.lesser).updateHeight(); if (p.greater != null) ((AVLNode) p.greater).updateHeight(); p.updateHeight(); } } } } /** * {@inheritDoc} */ @Override protected boolean validateNode(Node node) { boolean bst = super.validateNode(node); if (!bst) return false; AVLNode avlNode = (AVLNode) node; int balanceFactor = avlNode.getBalanceFactor(); if (balanceFactor > 1 || balanceFactor < -1) { return false; } if (avlNode.isLeaf()) { if (avlNode.height != 1) return false; } else { AVLNode avlNodeLesser = (AVLNode) avlNode.lesser; int lesserHeight = 1; if (avlNodeLesser != null) lesserHeight = avlNodeLesser.height; AVLNode avlNodeGreater = (AVLNode) avlNode.greater; int greaterHeight = 1; if (avlNodeGreater != null) greaterHeight = avlNodeGreater.height; if (avlNode.height == (lesserHeight + 1) || avlNode.height == (greaterHeight + 1)) return true; return false; } return true; } /** * {@inheritDoc} */ @Override public String toString() { return AVLTreePrinter.getString(this); } protected static class AVLNode> extends Node { protected int height = 1; /** * Constructor for an AVL node * * @param parent * Parent of the node in the tree, can be NULL. * @param value * Value of the node in the tree. */ protected AVLNode(Node parent, T value) { super(parent, value); } /** * Determines is this node is a leaf (has no children). * * @return True if this node is a leaf. */ protected boolean isLeaf() { return ((lesser == null) && (greater == null)); } /** * Updates the height of this node based on it's children. */ protected int updateHeight() { int lesserHeight = 0; if (lesser != null) { AVLNode lesserAVLNode = (AVLNode) lesser; lesserHeight = lesserAVLNode.height; } int greaterHeight = 0; if (greater != null) { AVLNode greaterAVLNode = (AVLNode) greater; greaterHeight = greaterAVLNode.height; } if (lesserHeight > greaterHeight) { height = lesserHeight + 1; } else { height = greaterHeight + 1; } return height; } /** * Get the balance factor for this node. * * @return An integer representing the balance factor for this node. It * will be negative if the lesser branch is longer than the * greater branch. */ protected int getBalanceFactor() { int lesserHeight = 0; if (lesser != null) { AVLNode lesserAVLNode = (AVLNode) lesser; lesserHeight = lesserAVLNode.height; } int greaterHeight = 0; if (greater != null) { AVLNode greaterAVLNode = (AVLNode) greater; greaterHeight = greaterAVLNode.height; } return greaterHeight - lesserHeight; } /** * {@inheritDoc} */ @Override public String toString() { return "value=" + id + " height=" + height + " parent=" + ((parent != null) ? parent.id : "NULL") + " lesser=" + ((lesser != null) ? lesser.id : "NULL") + " greater=" + ((greater != null) ? greater.id : "NULL"); } } protected static class AVLTreePrinter { public static > String getString(AVLTree tree) { if (tree.root == null) return "Tree has no nodes."; return getString((AVLNode) tree.root, "", true); } public static > String getString(AVLNode node) { if (node == null) return "Sub-tree has no nodes."; return getString(node, "", true); } private static > String getString(AVLNode node, String prefix, boolean isTail) { StringBuilder builder = new StringBuilder(); builder.append(prefix + (isTail ? "└── " : "├── ") + "(" + node.height + ") " + node.id + "\n"); List> children = null; if (node.lesser != null || node.greater != null) { children = new ArrayList>(2); if (node.lesser != null) children.add(node.lesser); if (node.greater != null) children.add(node.greater); } if (children != null) { for (int i = 0; i < children.size() - 1; i++) { builder.append(getString((AVLNode) children.get(i), prefix + (isTail ? " " : "│ "), false)); } if (children.size() >= 1) { builder.append(getString((AVLNode) children.get(children.size() - 1), prefix + (isTail ? " " : "│ "), true)); } } return builder.toString(); } } }