/* * AvlTree.java * * Copyright (C) 2014 Leo Osvald <leo.osvald@gmail.com> * * This file is part of YOUR PROGRAM NAME. * * YOUR PROGRAM NAME is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * YOUR PROGRAM NAME is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with YOUR PROGRAM NAME. If not, see <http://www.gnu.org/licenses/>. */ package org.sglj.util.struct; import java.util.AbstractSet; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; import org.sglj.math.discrete.CodomainMergeable; import org.sglj.util.ArrayStack; import org.sglj.util.Stack; import org.sglj.util.ref.IntReference; public abstract class AvlTree<E> extends AbstractSet<E> { protected static final Comparator<Object> minComparator = new Comparator<Object>() { @SuppressWarnings("unchecked") @Override public int compare(Object o1, Object o2) { return ((AvlNode<Object>)o2).left == null ? 0 : -1; } }; private Comparator<Object> comparator; private int size; private AvlNode<E> root; public AvlTree() { this(new Comparator<E>() { @SuppressWarnings("unchecked") @Override public int compare(E o1, E o2) { return ((Comparable<E>)o1).compareTo(o2); } }); } public AvlTree(Comparator<? super E> comparator) { setComparator(comparator); } protected void setComparator(final Comparator<? super E> comparator) { this.comparator = new Comparator<Object>() { @SuppressWarnings("unchecked") @Override public int compare(Object o1, Object o2) { return comparator.compare((E)o1, ((AvlNode<E>)o2).getKey()); } }; } protected Comparator<Object> getComparator() { return comparator; } protected abstract AvlNode<E> createNode(E item); protected static class InsertResult<E> extends IntReference { public AvlNode<E> inserted; public InsertResult() { super(0); } } public void insert(E item, InsertResult<E> result) { root = insert(root, item, result); if (result.inserted != null) ++size; } private AvlNode<E> insert(AvlNode<E> root, E key, InsertResult<E> result) { if (root == null) { // insert a new node result.value = 1; (result.inserted = createNode(key)).incrementMultiplicity(key); return result.inserted; } // Compare items and determine which direction to search int cmp = root.compareKey(key, comparator); if (cmp == 0) { // key already in tree at this node root.incrementMultiplicity(key); result.value = 0; result.inserted = null; return root; } // Insert into left/right subtree if (cmp < 0) root.left = insert(root.left, key, result); else root.right = insert(root.right, key, result); if (result.inserted != null) { // rebalance if needed -- height of current tree increases only if its // subtree height increases and the current tree needs no rotation. if (result.value != 0 && (root.myBal += cmp < 0 ? -1 : 1) != 0) { root = root.rebalance(result); result.value = 1 - result.value; } else result.value = 0; root.mergeCodomains(root.left, root.right); } return root; } public static class RemoveResult<K> extends IntReference { public AvlNode<K> removed; public RemoveResult() { super(0); } } public void remove(E key, RemoveResult<E> result) { result.removed = null; root = AvlNode.remove(root, key, comparator, result); if (result.removed != null) --size; } public AvlNode<E> getRoot() { return root; } @Override public int size() { return size; } @Override public Iterator<E> iterator() { return new AvlIterator(); } @SuppressWarnings("unchecked") @Override public boolean contains(Object o) { return AvlNode.search(root, (E)o, comparator) != null; } @Override public boolean add(E e) { InsertResult<E> result = new InsertResult<E>(); insert(e, result); return result.inserted != null; } @SuppressWarnings("unchecked") @Override public boolean addAll(Collection<? extends E> c) { boolean modified = false; InsertResult<E> result = new InsertResult<E>(); for (Object item : c) { insert((E)item, result); modified |= (result.inserted != null); } return modified; } @SuppressWarnings("unchecked") @Override public boolean remove(Object o) { RemoveResult<E> result = new RemoveResult<E>(); remove((E)o, result); return result.removed != null; } @SuppressWarnings("unchecked") @Override public boolean removeAll(Collection<?> c) { int oldSize = size(); RemoveResult<E> result = new RemoveResult<E>(); for (Object item : c) remove((E)item, result); return size() != oldSize; } @Override public void clear() { size = 0; root = null; } protected static abstract class AvlNode<K> implements CodomainMergeable<AvlNode<K>> { protected AvlNode<K> left; protected AvlNode<K> right; private int myBal; protected AvlNode<K> rotateLeft(IntReference heightChange) { AvlNode<K> root = this; AvlNode<K> oldRoot = root; heightChange.value = (root.right.myBal == 0 ? 0 : 1); // perform rotation root = root.right; oldRoot.right = root.left; root.left = oldRoot; // update balances oldRoot.myBal = -(--root.myBal); oldRoot.mergeCodomains(oldRoot.left, oldRoot.right); root.mergeCodomains(root.left, root.right); return root; } protected AvlNode<K> rotateRight(IntReference heightChange) { AvlNode<K> root = this; AvlNode<K> oldRoot = root; heightChange.value = (root.left.myBal == 0 ? 0 : 1); // perform rotation root = root.left; oldRoot.left = root.right; root.right = oldRoot; // update balances oldRoot.myBal = -(++root.myBal); oldRoot.mergeCodomains(oldRoot.left, oldRoot.right); root.mergeCodomains(root.left, root.right); return root; } protected AvlNode<K> rotateLeftRight(IntReference heightChange) { AvlNode<K> root = this; AvlNode<K> oldRoot = root; AvlNode<K> oldOtherDirSubtree = root.right; // assign a new root root = oldRoot.right.left; // new root exchanges its left subtree for its grandparent oldRoot.right = root.left; root.left = oldRoot; // new root exchanges its right subtree for its parent oldOtherDirSubtree.left = root.right; root.right = oldOtherDirSubtree; heightChange.value = updateBalancesRotateTwice(root); oldRoot.mergeCodomains(oldRoot.left, oldRoot.right); AvlNode<K> rootNewChild = root.right; rootNewChild.mergeCodomains(rootNewChild.left, rootNewChild.right); root.mergeCodomains(root.left, root.right); return root; } protected AvlNode<K> rotateRightLeft(IntReference heightChange) { AvlNode<K> root = this; AvlNode<K> oldRoot = root; AvlNode<K> oldOtherDirSubtree = root.left; // assign a new root root = oldRoot.left.right; // the new root exchanges its right subtree for its grandparent oldRoot.left = root.right; root.right = oldRoot; // the new root exchanges its left subtree for its parent oldOtherDirSubtree.right = root.left; root.left = oldOtherDirSubtree; heightChange.value = updateBalancesRotateTwice(root); oldRoot.mergeCodomains(oldRoot.left, oldRoot.right); AvlNode<K> rootNewChild = root.left; rootNewChild.mergeCodomains(rootNewChild.left, rootNewChild.right); root.mergeCodomains(root.left, root.right); return root; } private static <K> int updateBalancesRotateTwice(AvlNode<K> root) { // update balances root.left.myBal = -Math.max(root.myBal, 0); root.right.myBal = -Math.min(root.myBal, 0); root.myBal = 0; // a double rotation always shortens the overall height of the tree return 1; } public AvlNode<K> rebalance(IntReference heightChange) { AvlNode<K> root = this; if (!isLeftBalanced()) { if (root.isRightHeavy()) { root = root.rotateRightLeft(heightChange); } else { root = root.rotateRight(heightChange); } } else if (!isRightBalanced()) { if (root.isLeftHeavy()) { root = root.rotateLeftRight(heightChange); } else { root = root.rotateLeft(heightChange); } } else heightChange.value = 0; return root; } public int compareKey(K target, Comparator<Object> comparator) { return comparator.compare(target, this); } public abstract K getKey(); public boolean isLeftBalanced() { return myBal >= -1; } public boolean isRightBalanced() { return myBal <= 1; } public boolean isLeftHeavy() { return right.myBal == -1; } public boolean isRightHeavy() { return left.myBal == 1; } public static <K> AvlNode<K> search(AvlNode<K> root, K key, Comparator<Object> comparator) { while (root != null) { int cmp = root.compareKey(key, comparator); if (cmp == 0) break; else if (cmp < 0) root = root.left; else root = root.right; } return root; } public static <K> AvlNode<K> remove(AvlNode<K> root, K key, Comparator<Object> comparator, RemoveResult<K> result) { if (root == null) { // Key not found result.value = 0; return root; } // Compare items and determine which direction to search int decrease = 0; int cmp = root.compareKey(key, comparator); if (cmp < 0) { root.left = remove(root.left, key, comparator, result); decrease = -result.value; } else if (cmp > 0) { root.right = remove(root.right, key, comparator, result); decrease = result.value; } else { // Found key at this node if (result.removed == null && !root.decrementMultiplicity(key)) { result.value = 0; return root; } // At this point, we know "result" is zero and "root" points to // the node that we need to delete. There are three cases: // // 1) The node is a leaf. Remove it and return. // // 2) The node is a branch (has only 1 child). Make "root" // (the pointer to this node) point to the child. // // 3) The node has two children. Swap items with root's // successor (the smallest item in its right subtree) and // delete the successor from the root's right subtree. // Reseat "decrease" if the subtree height decreased // due to the deletion of the successor of "root". boolean existsLeft = (root.left != null); boolean existsRight = (root.right != null); if (!existsLeft || !existsRight) { AvlNode<K> toDelete = root; if (existsLeft) root = root.left; else if (existsRight) root = root.right; else { // We have a leaf -- remove it result.removed = toDelete; result.value = 1; // height changed from 1 to 0 return null; } // We have one child -- only child becomes new root result.removed = toDelete; result.value = 1; // We just shortened the subtree // Null-out the subtree pointers not to delete recursively toDelete.left = toDelete.right = null; return root; } else { // We have two children -- find successor and replace our // current data item with that of the successor int heightChange = result.value; result.removed = root; AvlNode<K> right = remove(root.right, key, minComparator, result); AvlNode<K> succ = result.removed; succ.left = root.left; succ.right = right; succ.myBal = root.myBal; // XXX: double-check if correct decrease = result.value; result.value = heightChange; root = succ; } } // Rebalance if necessary -- the height of current tree changes if: // (1) a rotation that was performed changed the subtree height // (2) the decreased subtree height now matches the sibling's height // (now, the current has a zero balance when it previously did not). if (decrease != 0) { // update balance factor and rebalance if the height changed if ((root.myBal -= decrease) != 0) { root = root.rebalance(result); } else { result.value = 1; // balanced because subtree decreased } } else { result.value = 0; } root.mergeCodomains(root.left, root.right); return root; } public void incrementMultiplicity(K key) { } public boolean decrementMultiplicity(K key) { return true; } } private class AvlIterator implements Iterator<E> { AvlNode<E> cur = root; boolean done = (root == null); Stack<AvlNode<E>> stack = new ArrayStack<AvlNode<E>>(); @Override public boolean hasNext() { return !done; } @Override public E next() { while (cur != null) { stack.push(cur); cur = cur.left; } if (stack.isEmpty()) throw new NoSuchElementException(); AvlNode<E> prev = stack.pop(); cur = prev.right; done = (cur == null && stack.isEmpty()); return prev.getKey(); } @Override public void remove() { throw new UnsupportedOperationException("Not implemented"); } } }