/******************************************************************************
* Compilation: javac AVLTreeST.java
* Execution: java AVLTreeST < input.txt
* Dependencies: StdIn.java StdOut.java
* Data files: https://algs4.cs.princeton.edu/33balanced/tinyST.txt
*
* A symbol table implemented using an AVL tree.
*
* % more tinyST.txt
* S E A R C H E X A M P L E
*
* % java AVLTreeST < tinyST.txt
* A 8
* C 4
* E 12
* H 5
* L 11
* M 9
* P 10
* R 3
* S 0
* X 7
*
******************************************************************************/
package edu.princeton.cs.algs4;
import java.util.NoSuchElementException;
/**
* The {@code AVLTreeST} class represents an ordered symbol table of
* generic key-value pairs. It supports the usual put, get,
* contains, delete, size, and is-empty
* methods. It also provides ordered methods for finding the minimum,
* maximum, floor, and ceiling. It also provides a
* keys method for iterating over all of the keys. A symbol table
* implements the associative array abstraction: when associating a
* value with a key that is already in the symbol table, the convention is to
* replace the old value with the new value. Unlike {@link java.util.Map}, this
* class uses the convention that values cannot be {@code null}
* —setting the value associated with a key to {@code null} is
* equivalent to deleting the key from the symbol table.
*
* This symbol table implementation uses internally an
* AVL tree (Georgy
* Adelson-Velsky and Evgenii Landis' tree) which is a self-balancing BST.
* In an AVL tree, the heights of the two child subtrees of any
* node differ by at most one; if at any time they differ by more than one,
* rebalancing is done to restore this property.
*
* This implementation requires that the key type implements the
* {@code Comparable} interface and calls the {@code compareTo()} and
* method to compare two keys. It does not call either {@code equals()} or
* {@code hashCode()}. The put, get, contains,
* delete, minimum, maximum, ceiling, and
* floor operations each take logarithmic time in the worst case. The
* size, and is-empty operations take constant time.
* Construction also takes constant time.
*
* For other implementations of the same API, see {@link ST}, {@link BinarySearchST},
* {@link SequentialSearchST}, {@link BST}, {@link RedBlackBST},
* {@link SeparateChainingHashST}, and {@link LinearProbingHashST}.
*
* @author Marcelo Silva
*/
public class AVLTreeST, Value> {
/**
* The root node.
*/
private Node root;
/**
* This class represents an inner node of the AVL tree.
*/
private class Node {
private final Key key; // the key
private Value val; // the associated value
private int height; // height of the subtree
private int size; // number of nodes in subtree
private Node left; // left subtree
private Node right; // right subtree
public Node(Key key, Value val, int height, int size) {
this.key = key;
this.val = val;
this.size = size;
this.height = height;
}
}
/**
* Initializes an empty symbol table.
*/
public AVLTreeST() {
}
/**
* Checks if the symbol table is empty.
*
* @return {@code true} if the symbol table is empty.
*/
public boolean isEmpty() {
return root == null;
}
/**
* Returns the number key-value pairs in the symbol table.
*
* @return the number key-value pairs in the symbol table
*/
public int size() {
return size(root);
}
/**
* Returns the number of nodes in the subtree.
*
* @param x the subtree
*
* @return the number of nodes in the subtree
*/
private int size(Node x) {
if (x == null) return 0;
return x.size;
}
/**
* Returns the height of the internal AVL tree. It is assumed that the
* height of an empty tree is -1 and the height of a tree with just one node
* is 0.
*
* @return the height of the internal AVL tree
*/
public int height() {
return height(root);
}
/**
* Returns the height of the subtree.
*
* @param x the subtree
*
* @return the height of the subtree.
*/
private int height(Node x) {
if (x == null) return -1;
return x.height;
}
/**
* Returns the value associated with the given key.
*
* @param key the key
* @return the value associated with the given key if the key is in the
* symbol table and {@code null} if the key is not in the
* symbol table
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public Value get(Key key) {
if (key == null) throw new IllegalArgumentException("argument to get() is null");
Node x = get(root, key);
if (x == null) return null;
return x.val;
}
/**
* Returns value associated with the given key in the subtree or
* {@code null} if no such key.
*
* @param x the subtree
* @param key the key
* @return value associated with the given key in the subtree or
* {@code null} if no such key
*/
private Node get(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) return get(x.left, key);
else if (cmp > 0) return get(x.right, key);
else return x;
}
/**
* Checks if the symbol table contains the given key.
*
* @param key the key
* @return {@code true} if the symbol table contains {@code key}
* and {@code false} otherwise
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public boolean contains(Key key) {
return get(key) != null;
}
/**
* Inserts the specified key-value pair into the symbol table, overwriting
* the old value with the new value if the symbol table already contains the
* specified key. Deletes the specified key (and its associated value) from
* this symbol table if the specified value is {@code null}.
*
* @param key the key
* @param val the value
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public void put(Key key, Value val) {
if (key == null) throw new IllegalArgumentException("first argument to put() is null");
if (val == null) {
delete(key);
return;
}
root = put(root, key, val);
assert check();
}
/**
* Inserts the key-value pair in the subtree. It overrides the old value
* with the new value if the symbol table already contains the specified key
* and deletes the specified key (and its associated value) from this symbol
* table if the specified value is {@code null}.
*
* @param x the subtree
* @param key the key
* @param val the value
* @return the subtree
*/
private Node put(Node x, Key key, Value val) {
if (x == null) return new Node(key, val, 0, 1);
int cmp = key.compareTo(x.key);
if (cmp < 0) {
x.left = put(x.left, key, val);
}
else if (cmp > 0) {
x.right = put(x.right, key, val);
}
else {
x.val = val;
return x;
}
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
return balance(x);
}
/**
* Restores the AVL tree property of the subtree.
*
* @param x the subtree
* @return the subtree with restored AVL property
*/
private Node balance(Node x) {
if (balanceFactor(x) < -1) {
if (balanceFactor(x.right) > 0) {
x.right = rotateRight(x.right);
}
x = rotateLeft(x);
}
else if (balanceFactor(x) > 1) {
if (balanceFactor(x.left) < 0) {
x.left = rotateLeft(x.left);
}
x = rotateRight(x);
}
return x;
}
/**
* Returns the balance factor of the subtree. The balance factor is defined
* as the difference in height of the left subtree and right subtree, in
* this order. Therefore, a subtree with a balance factor of -1, 0 or 1 has
* the AVL property since the heights of the two child subtrees differ by at
* most one.
*
* @param x the subtree
* @return the balance factor of the subtree
*/
private int balanceFactor(Node x) {
return height(x.left) - height(x.right);
}
/**
* Rotates the given subtree to the right.
*
* @param x the subtree
* @return the right rotated subtree
*/
private Node rotateRight(Node x) {
Node y = x.left;
x.left = y.right;
y.right = x;
y.size = x.size;
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
y.height = 1 + Math.max(height(y.left), height(y.right));
return y;
}
/**
* Rotates the given subtree to the left.
*
* @param x the subtree
* @return the left rotated subtree
*/
private Node rotateLeft(Node x) {
Node y = x.right;
x.right = y.left;
y.left = x;
y.size = x.size;
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
y.height = 1 + Math.max(height(y.left), height(y.right));
return y;
}
/**
* Removes the specified key and its associated value from the symbol table
* (if the key is in the symbol table).
*
* @param key the key
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("argument to delete() is null");
if (!contains(key)) return;
root = delete(root, key);
assert check();
}
/**
* Removes the specified key and its associated value from the given
* subtree.
*
* @param x the subtree
* @param key the key
* @return the updated subtree
*/
private Node delete(Node x, Key key) {
int cmp = key.compareTo(x.key);
if (cmp < 0) {
x.left = delete(x.left, key);
}
else if (cmp > 0) {
x.right = delete(x.right, key);
}
else {
if (x.left == null) {
return x.right;
}
else if (x.right == null) {
return x.left;
}
else {
Node y = x;
x = min(y.right);
x.right = deleteMin(y.right);
x.left = y.left;
}
}
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
return balance(x);
}
/**
* Removes the smallest key and associated value from the symbol table.
*
* @throws NoSuchElementException if the symbol table is empty
*/
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("called deleteMin() with empty symbol table");
root = deleteMin(root);
assert check();
}
/**
* Removes the smallest key and associated value from the given subtree.
*
* @param x the subtree
* @return the updated subtree
*/
private Node deleteMin(Node x) {
if (x.left == null) return x.right;
x.left = deleteMin(x.left);
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
return balance(x);
}
/**
* Removes the largest key and associated value from the symbol table.
*
* @throws NoSuchElementException if the symbol table is empty
*/
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("called deleteMax() with empty symbol table");
root = deleteMax(root);
assert check();
}
/**
* Removes the largest key and associated value from the given subtree.
*
* @param x the subtree
* @return the updated subtree
*/
private Node deleteMax(Node x) {
if (x.right == null) return x.left;
x.right = deleteMax(x.right);
x.size = 1 + size(x.left) + size(x.right);
x.height = 1 + Math.max(height(x.left), height(x.right));
return balance(x);
}
/**
* Returns the smallest key in the symbol table.
*
* @return the smallest key in the symbol table
* @throws NoSuchElementException if the symbol table is empty
*/
public Key min() {
if (isEmpty()) throw new NoSuchElementException("called min() with empty symbol table");
return min(root).key;
}
/**
* Returns the node with the smallest key in the subtree.
*
* @param x the subtree
* @return the node with the smallest key in the subtree
*/
private Node min(Node x) {
if (x.left == null) return x;
return min(x.left);
}
/**
* Returns the largest key in the symbol table.
*
* @return the largest key in the symbol table
* @throws NoSuchElementException if the symbol table is empty
*/
public Key max() {
if (isEmpty()) throw new NoSuchElementException("called max() with empty symbol table");
return max(root).key;
}
/**
* Returns the node with the largest key in the subtree.
*
* @param x the subtree
* @return the node with the largest key in the subtree
*/
private Node max(Node x) {
if (x.right == null) return x;
return max(x.right);
}
/**
* Returns the largest key in the symbol table less than or equal to
* {@code key}.
*
* @param key the key
* @return the largest key in the symbol table less than or equal to
* {@code key}
* @throws NoSuchElementException if the symbol table is empty
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public Key floor(Key key) {
if (key == null) throw new IllegalArgumentException("argument to floor() is null");
if (isEmpty()) throw new NoSuchElementException("called floor() with empty symbol table");
Node x = floor(root, key);
if (x == null) return null;
else return x.key;
}
/**
* Returns the node in the subtree with the largest key less than or equal
* to the given key.
*
* @param x the subtree
* @param key the key
* @return the node in the subtree with the largest key less than or equal
* to the given key
*/
private Node floor(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp < 0) return floor(x.left, key);
Node y = floor(x.right, key);
if (y != null) return y;
else return x;
}
/**
* Returns the smallest key in the symbol table greater than or equal to
* {@code key}.
*
* @param key the key
* @return the smallest key in the symbol table greater than or equal to
* {@code key}
* @throws NoSuchElementException if the symbol table is empty
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public Key ceiling(Key key) {
if (key == null) throw new IllegalArgumentException("argument to ceiling() is null");
if (isEmpty()) throw new NoSuchElementException("called ceiling() with empty symbol table");
Node x = ceiling(root, key);
if (x == null) return null;
else return x.key;
}
/**
* Returns the node in the subtree with the smallest key greater than or
* equal to the given key.
*
* @param x the subtree
* @param key the key
* @return the node in the subtree with the smallest key greater than or
* equal to the given key
*/
private Node ceiling(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp > 0) return ceiling(x.right, key);
Node y = ceiling(x.left, key);
if (y != null) return y;
else return x;
}
/**
* Returns the kth smallest key in the symbol table.
*
* @param k the order statistic
* @return the kth smallest key in the symbol table
* @throws IllegalArgumentException unless {@code k} is between 0 and
* {@code size() -1 }
*/
public Key select(int k) {
if (k < 0 || k >= size()) throw new IllegalArgumentException("k is not in range 0-" + (size() - 1));
Node x = select(root, k);
return x.key;
}
/**
* Returns the node with key the kth smallest key in the subtree.
*
* @param x the subtree
* @param k the kth smallest key in the subtree
* @return the node with key the kth smallest key in the subtree
*/
private Node select(Node x, int k) {
if (x == null) return null;
int t = size(x.left);
if (t > k) return select(x.left, k);
else if (t < k) return select(x.right, k - t - 1);
else return x;
}
/**
* Returns the number of keys in the symbol table strictly less than
* {@code key}.
*
* @param key the key
* @return the number of keys in the symbol table strictly less than
* {@code key}
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public int rank(Key key) {
if (key == null) throw new IllegalArgumentException("argument to rank() is null");
return rank(key, root);
}
/**
* Returns the number of keys in the subtree less than key.
*
* @param key the key
* @param x the subtree
* @return the number of keys in the subtree less than key
*/
private int rank(Key key, Node x) {
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) return rank(key, x.left);
else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
else return size(x.left);
}
/**
* Returns all keys in the symbol table.
*
* @return all keys in the symbol table
*/
public Iterable keys() {
return keysInOrder();
}
/**
* Returns all keys in the symbol table following an in-order traversal.
*
* @return all keys in the symbol table following an in-order traversal
*/
public Iterable keysInOrder() {
Queue queue = new Queue();
keysInOrder(root, queue);
return queue;
}
/**
* Adds the keys in the subtree to queue following an in-order traversal.
*
* @param x the subtree
* @param queue the queue
*/
private void keysInOrder(Node x, Queue queue) {
if (x == null) return;
keysInOrder(x.left, queue);
queue.enqueue(x.key);
keysInOrder(x.right, queue);
}
/**
* Returns all keys in the symbol table following a level-order traversal.
*
* @return all keys in the symbol table following a level-order traversal.
*/
public Iterable keysLevelOrder() {
Queue queue = new Queue();
if (!isEmpty()) {
Queue queue2 = new Queue();
queue2.enqueue(root);
while (!queue2.isEmpty()) {
Node x = queue2.dequeue();
queue.enqueue(x.key);
if (x.left != null) {
queue2.enqueue(x.left);
}
if (x.right != null) {
queue2.enqueue(x.right);
}
}
}
return queue;
}
/**
* Returns all keys in the symbol table in the given range.
*
* @param lo the lowest key
* @param hi the highest key
* @return all keys in the symbol table between {@code lo} (inclusive)
* and {@code hi} (exclusive)
* @throws IllegalArgumentException if either {@code lo} or {@code hi}
* is {@code null}
*/
public Iterable keys(Key lo, Key hi) {
if (lo == null) throw new IllegalArgumentException("first argument to keys() is null");
if (hi == null) throw new IllegalArgumentException("second argument to keys() is null");
Queue queue = new Queue();
keys(root, queue, lo, hi);
return queue;
}
/**
* Adds the keys between {@code lo} and {@code hi} in the subtree
* to the {@code queue}.
*
* @param x the subtree
* @param queue the queue
* @param lo the lowest key
* @param hi the highest key
*/
private void keys(Node x, Queue queue, Key lo, Key hi) {
if (x == null) return;
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if (cmplo < 0) keys(x.left, queue, lo, hi);
if (cmplo <= 0 && cmphi >= 0) queue.enqueue(x.key);
if (cmphi > 0) keys(x.right, queue, lo, hi);
}
/**
* Returns the number of keys in the symbol table in the given range.
*
* @param lo minimum endpoint
* @param hi maximum endpoint
* @return the number of keys in the symbol table between {@code lo}
* (inclusive) and {@code hi} (exclusive)
* @throws IllegalArgumentException if either {@code lo} or {@code hi}
* is {@code null}
*/
public int size(Key lo, Key hi) {
if (lo == null) throw new IllegalArgumentException("first argument to size() is null");
if (hi == null) throw new IllegalArgumentException("second argument to size() is null");
if (lo.compareTo(hi) > 0) return 0;
if (contains(hi)) return rank(hi) - rank(lo) + 1;
else return rank(hi) - rank(lo);
}
/**
* Checks if the AVL tree invariants are fine.
*
* @return {@code true} if the AVL tree invariants are fine
*/
private boolean check() {
if (!isBST()) StdOut.println("Symmetric order not consistent");
if (!isAVL()) StdOut.println("AVL property not consistent");
if (!isSizeConsistent()) StdOut.println("Subtree counts not consistent");
if (!isRankConsistent()) StdOut.println("Ranks not consistent");
return isBST() && isAVL() && isSizeConsistent() && isRankConsistent();
}
/**
* Checks if AVL property is consistent.
*
* @return {@code true} if AVL property is consistent.
*/
private boolean isAVL() {
return isAVL(root);
}
/**
* Checks if AVL property is consistent in the subtree.
*
* @param x the subtree
* @return {@code true} if AVL property is consistent in the subtree
*/
private boolean isAVL(Node x) {
if (x == null) return true;
int bf = balanceFactor(x);
if (bf > 1 || bf < -1) return false;
return isAVL(x.left) && isAVL(x.right);
}
/**
* Checks if the symmetric order is consistent.
*
* @return {@code true} if the symmetric order is consistent
*/
private boolean isBST() {
return isBST(root, null, null);
}
/**
* Checks if the tree rooted at x is a BST with all keys strictly between
* min and max (if min or max is null, treat as empty constraint) Credit:
* Bob Dondero's elegant solution
*
* @param x the subtree
* @param min the minimum key in subtree
* @param max the maximum key in subtree
* @return {@code true} if if the symmetric order is consistent
*/
private boolean isBST(Node x, Key min, Key max) {
if (x == null) return true;
if (min != null && x.key.compareTo(min) <= 0) return false;
if (max != null && x.key.compareTo(max) >= 0) return false;
return isBST(x.left, min, x.key) && isBST(x.right, x.key, max);
}
/**
* Checks if size is consistent.
*
* @return {@code true} if size is consistent
*/
private boolean isSizeConsistent() {
return isSizeConsistent(root);
}
/**
* Checks if the size of the subtree is consistent.
*
* @return {@code true} if the size of the subtree is consistent
*/
private boolean isSizeConsistent(Node x) {
if (x == null) return true;
if (x.size != size(x.left) + size(x.right) + 1) return false;
return isSizeConsistent(x.left) && isSizeConsistent(x.right);
}
/**
* Checks if rank is consistent.
*
* @return {@code true} if rank is consistent
*/
private boolean isRankConsistent() {
for (int i = 0; i < size(); i++)
if (i != rank(select(i))) return false;
for (Key key : keys())
if (key.compareTo(select(rank(key))) != 0) return false;
return true;
}
/**
* Unit tests the {@code AVLTreeST} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
AVLTreeST st = new AVLTreeST();
for (int i = 0; !StdIn.isEmpty(); i++) {
String key = StdIn.readString();
st.put(key, i);
}
for (String s : st.keys())
StdOut.println(s + " " + st.get(s));
StdOut.println();
}
}
/******************************************************************************
* Copyright 2002-2022, Robert Sedgewick and Kevin Wayne.
*
* This file is part of algs4.jar, which accompanies the textbook
*
* Algorithms, 4th edition by Robert Sedgewick and Kevin Wayne,
* Addison-Wesley Professional, 2011, ISBN 0-321-57351-X.
* http://algs4.cs.princeton.edu
*
*
* algs4.jar 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.
*
* algs4.jar 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 algs4.jar. If not, see http://www.gnu.org/licenses.
******************************************************************************/