Question: AVL trees. An AVL tree is a BST where the height of every node and that of its sibling differ by at most 1. (The oldest balanced tree algorithms are based on using rotations to maintain height balance in AVL trees.) Show that coloring red links that go from nodes of even height to nodes of odd height in an AVL tree gives a (perfectly balanced) 2-3-4 tree, where red links are not necessarily left-leaning. Extra credit: Develop an implementation of the symbol-table API that uses this as the underlying data structure. One approach is to keep a height field in each node, using rotations after the recursive calls to adjust the height as necessary; another is to use the red-black representation and use methods like moveRedLeft() and moveRedRight() in Exercise 3.3.39 and Exercise 3.3.40.

Answer:
// Exercise32_AVLTrees.java
package chapter3.section3;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 29/06/17.
 */
// Based on http://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/AVLTreeST.java.html
// Thanks to williamcheng-web (https://github.com/williamcheng-web) for finding a bug on the height update after a rotation:
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/162
public class Exercise32_AVLTrees {

    private class AVLTree<Key extends Comparable<Key>, Value> {

        private class Node {
            Key key;
            Value value;
            Node left, right;

            int height;
            int size;

            Node(Key key, Value value, int size, int height) {
                this.key = key;
                this.value = value;

                this.size = size;
                this.height = height;
            }
        }

        private Node root;

        public int size() {
            return size(root);
        }

        private int size(Node node) {
            if (node == null) {
                return 0;
            }

            return node.size;
        }

        public int height() {
            return height(root);
        }

        private int height(Node node) {
            if (node == null) {
                return -1;
            }

            return node.height;
        }

        public boolean isEmpty() {
            return size(root) == 0;
        }

        private Node rotateLeft(Node node) {
            if (node == null || node.right == null) {
                return node;
            }

            Node newRoot = node.right;

            node.right = newRoot.left;
            newRoot.left = node;

            node.height = 1 + Math.max(height(node.left), height(node.right));
            newRoot.height = 1 + Math.max(height(newRoot.left), height(newRoot.right));

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private Node rotateRight(Node node) {
            if (node == null || node.left == null) {
                return node;
            }

            Node newRoot = node.left;

            node.left = newRoot.right;
            newRoot.right = node;

            node.height = 1 + Math.max(height(node.left), height(node.right));
            newRoot.height = 1 + Math.max(height(newRoot.left), height(newRoot.right));

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        public void put(Key key, Value value) {
            if (key == null) {
                return;
            }

            if (value == null) {
                delete(key);
                return;
            }

            root = put(root, key, value);
        }

        private Node put(Node node, Key key, Value value) {
            if (node == null) {
                return new Node(key, value, 1, 0);
            }

            int compare = key.compareTo(node.key);

            if (compare < 0) {
                node.left = put(node.left, key, value);
            } else if (compare > 0) {
                node.right = put(node.right, key, value);
            } else {
                node.value = value;
            }

            node.height = 1 + Math.max(height(node.left), height(node.right));
            node.size = size(node.left) + 1 + size(node.right);

            return balance(node);
        }

        private Node balance(Node node) {

            if (balanceFactor(node) < -1) {
                //right-left case
                if (balanceFactor(node.right) > 0) {
                    node.right = rotateRight(node.right);
                }
                node = rotateLeft(node);
            }

            if (balanceFactor(node) > 1) {
                //left-right case
                if (balanceFactor(node.left) < 0) {
                    node.left = rotateLeft(node.left);
                }
                node = rotateRight(node);
            }

            return node;
        }

        /**
         * 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.
         */
        private int balanceFactor(Node node) {
            if (node == null) {
                return 0;
            }
            return height(node.left) - height(node.right);
        }

        public Value get(Key key) {
            if (key == null) {
                return null;
            }

            return get(root, key);
        }

        private Value get(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return get(node.left, key);
            } else if (compare > 0) {
                return get(node.right, key);
            } else {
                return node.value;
            }
        }

        public boolean contains(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to contains() cannot be null");
            }
            return get(key) != null;
        }

        public Key min() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return min(root).key;
        }

        private Node min(Node node) {
            if (node.left == null) {
                return node;
            }

            return min(node.left);
        }

        public Key max() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return max(root).key;
        }

        private Node max(Node node) {
            if (node.right == null) {
                return node;
            }

            return max(node.right);
        }

        //Returns the highest key in the symbol table smaller than or equal to key.
        public Key floor(Key key) {
            Node node = floor(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node floor(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare < 0) {
                return floor(node.left, key);
            } else {
                Node rightNode = floor(node.right, key);
                if (rightNode != null) {
                    return rightNode;
                } else {
                    return node;
                }
            }
        }

        //Returns the smallest key in the symbol table greater than or equal to key.
        public Key ceiling(Key key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node ceiling(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare > 0) {
                return ceiling(node.right, key);
            } else {
                Node leftNode = ceiling(node.left, key);
                if (leftNode != null) {
                    return leftNode;
                } else {
                    return node;
                }
            }
        }

        public Key select(int index) {
            if (index >= size()) {
                throw new IllegalArgumentException("Index is higher than tree size");
            }

            return select(root, index).key;
        }

        private Node select(Node node, int index) {
            int leftSubtreeSize = size(node.left);

            if (leftSubtreeSize == index) {
                return node;
            } else if (leftSubtreeSize > index) {
                return select(node.left, index);
            } else {
                return select(node.right, index - leftSubtreeSize - 1);
            }
        }

        public int rank(Key key) {
            return rank(root, key);
        }

        private int rank(Node node, Key key) {
            if (node == null) {
                return 0;
            }

            //Returns the number of keys less than node.key in the subtree rooted at node
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return rank(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rank(node.right, key);
            } else {
                return size(node.left);
            }
        }

        public void deleteMin() {
            if (isEmpty()) {
                return;
            }

            root = deleteMin(root);
        }

        private Node deleteMin(Node node) {
            if (node.left == null) {
                return node.right;
            }

            node.left = deleteMin(node.left);

            node.size = size(node.left) + 1 + size(node.right);
            node.height = 1 + Math.max(height(node.left), height(node.right));
            return balance(node);
        }

        public void deleteMax() {
            if (isEmpty()) {
                return;
            }

            root = deleteMax(root);
        }

        private Node deleteMax(Node node) {
            if (node.right == null) {
                return node.left;
            }

            node.right = deleteMax(node.right);

            node.size = size(node.left) + 1 + size(node.right);
            node.height = 1 + Math.max(height(node.left), height(node.right));
            return balance(node);
        }

        public void delete(Key key) {
            if (isEmpty()) {
                return;
            }

            if (!contains(key)) {
                return;
            }

            root = delete(root, key);
        }

        private Node delete(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare < 0) {
                node.left = delete(node.left, key);
            } else if (compare > 0) {
                node.right = delete(node.right, key);
            } else {
                if (node.left == null) {
                    return node.right;
                } else if (node.right == null) {
                    return node.left;
                } else {
                    Node aux = min(node.right);
                    node.key = aux.key;
                    node.value = aux.value;
                    node.right = deleteMin(node.right);
                }
            }

            node.size = size(node.left) + 1 + size(node.right);
            node.height = 1 + Math.max(height(node.left), height(node.right));
            return balance(node);
        }

        public Iterable<Key> keys() {
            return keys(min(), max());
        }

        public Iterable<Key> keys(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to keys() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to keys() cannot be null");
            }

            Queue<Key> queue = new Queue<>();
            keys(root, queue, low, high);
            return queue;
        }

        private void keys(Node node, Queue<Key> queue, Key low, Key high) {
            if (node == null) {
                return;
            }

            int compareLow = low.compareTo(node.key);
            int compareHigh = high.compareTo(node.key);

            if (compareLow < 0) {
                keys(node.left, queue, low, high);
            }

            if (compareLow <= 0 && compareHigh >= 0) {
                queue.enqueue(node.key);
            }

            if (compareHigh > 0) {
                keys(node.right, queue, low, high);
            }
        }

        public int size(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to size() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to size() cannot be null");
            }

            if (low.compareTo(high) > 0) {
                return 0;
            }

            if (contains(high)) {
                return rank(high) - rank(low) + 1;
            } else {
                return rank(high) - rank(low);
            }
        }

        private boolean isAVL() {
            return isAVL(root);
        }

        private boolean isAVL(Node node) {
            if (node == null) {
                return true;
            }

            int balanceFactor = balanceFactor(node);
            if (balanceFactor < -1 || balanceFactor > 1) {
                return false;
            }

            return isAVL(node.left) && isAVL(node.right);
        }

        private boolean isSubtreeCountConsistent() {
            return isSubtreeCountConsistent(root);
        }

        private boolean isSubtreeCountConsistent(Node node) {
            if (node == null) {
                return true;
            }

            int totalSubtreeCount = 0;
            if (node.left != null) {
                totalSubtreeCount += node.left.size;
            }
            if (node.right != null) {
                totalSubtreeCount += node.right.size;
            }

            if (node.size != totalSubtreeCount + 1) {
                return false;
            }

            return isSubtreeCountConsistent(node.left) && isSubtreeCountConsistent(node.right);
        }
    }

    public static void main(String[] args) {
        Exercise32_AVLTrees avlTrees = new Exercise32_AVLTrees();
        AVLTree<Integer, Integer> avlTree = avlTrees.new AVLTree<>();

        avlTree.put(5, 5);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(1, 1);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(9, 9);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(2, 2);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(0, 0);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(99, 99);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(-1, -1);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(-2, -2);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(3, 3);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        avlTree.put(-5, -5);
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");

        StdOut.println("Size consistent: " + avlTree.isSubtreeCountConsistent() + " Expected: true\n");

        StdOut.println("Keys() test");

        for (Integer key : avlTree.keys()) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }
        StdOut.println("Expected: -5 -2 -1 0 1 2 3 5 9 99\n");

        // Test min()
        StdOut.println("Min key: " + avlTree.min() + " Expected: -5");

        // Test max()
        StdOut.println("Max key: " + avlTree.max() + " Expected: 99");

        // Test floor()
        StdOut.println("Floor of 5: " + avlTree.floor(5) + " Expected: 5");
        StdOut.println("Floor of 15: " + avlTree.floor(15) + " Expected: 9");

        // Test ceiling()
        StdOut.println("Ceiling of 5: " + avlTree.ceiling(5) + " Expected: 5");
        StdOut.println("Ceiling of 15: " + avlTree.ceiling(15) + " Expected: 99");

        // Test select()
        StdOut.println("Select key of rank 4: " + avlTree.select(4) + " Expected: 1");

        // Test rank()
        StdOut.println("Rank of key 9: " + avlTree.rank(9) + " Expected: 8");
        StdOut.println("Rank of key 10: " + avlTree.rank(10) + " Expected: 9");

        // Test delete()
        StdOut.println("\nDelete key 2");
        avlTree.delete(2);

        for (Integer key : avlTree.keys()) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        StdOut.println("Size consistent: " + avlTree.isSubtreeCountConsistent() + " Expected: true");

        // Test deleteMin()
        StdOut.println("\nDelete min (key -5)");
        avlTree.deleteMin();

        for (Integer key : avlTree.keys()) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        StdOut.println("Size consistent: " + avlTree.isSubtreeCountConsistent() + " Expected: true");

        // Test deleteMax()
        StdOut.println("\nDelete max (key 99)");
        avlTree.deleteMax();

        for (Integer key : avlTree.keys()) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }
        StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
        StdOut.println("Size consistent: " + avlTree.isSubtreeCountConsistent() + " Expected: true");

        // Test keys() with range
        StdOut.println("\nKeys in range [2, 10]");
        for (Integer key : avlTree.keys(2, 10)) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }

        StdOut.println("\nKeys in range [-4, -1]");
        for (Integer key : avlTree.keys(-4, -1)) {
            StdOut.println("Key " + key + ": " + avlTree.get(key));
        }

        // Delete all
        StdOut.println("\nDelete all");
        while (avlTree.size() > 0) {
            for (Integer key : avlTree.keys()) {
                StdOut.println("Key " + key + ": " + avlTree.get(key));
            }

            // avlTree.delete(avlTree.select(0));
            avlTree.delete(avlTree.select(avlTree.size() - 1));
            StdOut.println("Is AVL: " + avlTree.isAVL() + " Expected: true");
            StdOut.println("Size consistent: " + avlTree.isSubtreeCountConsistent() + " Expected: true");

            StdOut.println();
        }
    }
}

Additional explanation:
3.3.32 - AVL trees

Coloring red links that go from nodes of even height to nodes of odd height in an AVL tree gives a perfectly balanced 2-3-4 tree.
This happens because every two levels of the tree will merge its nodes into 4-nodes (or 3-nodes if the node has only one child or 2-nodes if the node has no children). This generates a perfectly balanced 2-3-4 tree.

For an AVL tree of size 6:

         (B)H
   (B)B        (B)V
(B)A  (B)C   (B)R

2-3 representation
    H
  B   V
 A C R

After coloring red links:

         (B)H
   (R)B        (R)V
(B)A  (B)C   (B)R

2-3 representation
    BHV
  A C   R

For an AVL tree of size 19:
                                  (B)P
                    (B)L                        (B)T
        (B)H              (B)N           (B)R          (B)V
   (B)F        (B)J    (B)M  (B)O     (B)Q   (B)S   (B)U  (B)X
(B)E  (B)G  (B)I  (B)K

2-3 representation
              P
        L           T
    H       N     R    V
 F    J   M   O  Q S  U X
E G  I K

After coloring red links:
                                  (B)P
                    (R)L                        (R)T
        (B)H              (B)N           (B)R          (B)V
   (R)F        (R)J    (R)M  (R)O     (R)Q   (R)S   (R)U  (R)X
(B)E  (B)G  (B)I  (B)K

2-3 representation
             LPT
    FHJ   MNO   QRS   UVX
 E G I K


Question: Certification. Add to RedBlackBST a method is23() to check that no node is connected to two red links and that there are no right-leaning red links and a method isBalanced() to check that all paths from the root to a null link have the same number of black links. Combine these methods with code from isBST() in Exercise 3.2.32 to create a method isRedBlackBST() that checks that the tree is a red-black BST.

Answer:
package chapter3.section3;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 29/06/17.
 */
@SuppressWarnings("unchecked")
public class Exercise33_Certification {

    private class RedBlackBSTCertification<Key extends Comparable<Key>, Value> extends RedBlackBST<Key, Value> {
        public boolean is23() {
            return is23(root);
        }

        private boolean is23(Node node) {
            if (node == null) {
                return true;
            }

            if (isRed(node.right)) {
                return false;
            }
            if (isRed(node) && isRed(node.left)) {
                return false;
            }

            return is23(node.left) && is23(node.right);
        }

        public boolean isBalanced() {
            int blackNodes = 0; // number of black links on path from root to min

            Node currentNode = root;
            while (currentNode != null) {
                if (!isRed(currentNode)) {
                    blackNodes++;
                }

                currentNode = currentNode.left;
            }

            return isBalanced(root, blackNodes);
        }

        private boolean isBalanced(Node node, int blackNodes) {
            if (node == null) {
                return blackNodes == 0;
            }

            if (!isRed(node)) {
                blackNodes--;
            }

            return isBalanced(node.left, blackNodes) && isBalanced(node.right, blackNodes);
        }

        public boolean isBST() {
            return isBST(root, null, null);
        }

        private boolean isBST(Node node, Comparable low, Comparable high) {
            if (node == null) {
                return true;
            }

            if (low != null && low.compareTo(node.key) >= 0) {
                return false;
            }
            if (high != null && high.compareTo(node.key) <= 0) {
                return false;
            }

            return isBST(node.left, low, node.key) && isBST(node.right, node.key, high);
        }

        public boolean isSizeConsistent() {
            return isSizeConsistent(root);
        }

        private boolean isSizeConsistent(Node node) {
            if (node == null) {
                return true;
            }
            int leftSize = node.left == null ? 0 : node.left.size;
            int rightSize = node.right == null ? 0 : node.right.size;
            return node.size == leftSize + rightSize + 1
                    && isSizeConsistent(node.left)
                    && isSizeConsistent(node.right);
        }

        public boolean isRedBlackBST() {
            return isBST() && isSizeConsistent() && is23() && isBalanced();
        }

    }

    public static void main(String[] args) {
        Exercise33_Certification certification = new Exercise33_Certification();
        RedBlackBSTCertification<Integer, String> redBlackBSTCertification = certification.new RedBlackBSTCertification<>();

        RedBlackBST.Node root = new RedBlackBST().new Node(10, "Value 10", 7, false);
        root.left = new RedBlackBST().new Node(5, "Value 5", 4, true);
        root.left.left = new RedBlackBST().new Node(2, "Value 2", 1, false);
        root.left.right = new RedBlackBST().new Node(9, "Value 9", 2, false);
        root.left.right.left = new RedBlackBST().new Node(7, "Value 7", 1, true);

        root.right = new RedBlackBST().new Node(14, "Value 14", 2, false);
        root.right.left = new RedBlackBST().new Node(11, "Value 11", 1, true);

        StdOut.println("Test 1");
        StdOut.println(redBlackBSTCertification.is23(root) + " Expected: true");
        StdOut.println(redBlackBSTCertification.isBalanced(root, 2) + " Expected: true");
        StdOut.println(redBlackBSTCertification.isBST(root, null, null) + " Expected: true\n");

        RedBlackBST.Node root2 = new RedBlackBST().new Node(20, "Value 20", 7, false);
        root2.left = new RedBlackBST().new Node(5, "Value 5", 4, true);
        root2.left.left = new RedBlackBST().new Node(2, "Value 2", 1, false);
        root2.left.right = new RedBlackBST().new Node(9, "Value 9", 2, false);
        root2.left.right.left = new RedBlackBST().new Node(1, "Value 1", 1, true); //Not a BST

        root2.right = new RedBlackBST().new Node(24, "Value 24", 2, false);
        root2.right.left = new RedBlackBST().new Node(21, "Value 21", 1, true);

        StdOut.println("Test 2");
        StdOut.println(redBlackBSTCertification.is23(root2) + " Expected: true");
        StdOut.println(redBlackBSTCertification.isBalanced(root2, 2) + " Expected: true");
        StdOut.println(redBlackBSTCertification.isBST(root2, null, null) + " Expected: false\n");

        RedBlackBST.Node root3 = new RedBlackBST().new Node(10, "Value 10", 7, false);
        root3.left = new RedBlackBST().new Node(5, "Value 5", 4, true);
        root3.left.left = new RedBlackBST().new Node(2, "Value 2", 1, false);
        root3.left.right = new RedBlackBST().new Node(9, "Value 9", 2, false);
        root3.left.right.left = new RedBlackBST().new Node(7, "Value 7", 1, true);

        root3.right = new RedBlackBST().new Node(14, "Value 14", 2, true); //Not 2-3 tree, not balanced
        root3.right.left = new RedBlackBST().new Node(11, "Value 11", 1, true);

        StdOut.println("Test 3");
        StdOut.println(redBlackBSTCertification.is23(root3) + " Expected: false");
        StdOut.println(redBlackBSTCertification.isBalanced(root3, 2) + " Expected: false");
        StdOut.println(redBlackBSTCertification.isBST(root3, null, null) + " Expected: true\n");

        RedBlackBST.Node root4 = new RedBlackBST().new Node(10, "Value 10", 7, false);
        root4.left = new RedBlackBST().new Node(5, "Value 5", 4, true);
        root4.left.left = new RedBlackBST().new Node(2, "Value 2", 1, true); //Not 2-3 tree, not balanced
        root4.left.right = new RedBlackBST().new Node(9, "Value 9", 2, false);
        root4.left.right.left = new RedBlackBST().new Node(7, "Value 7", 1, true);

        root4.right = new RedBlackBST().new Node(14, "Value 14", 2, false);
        root4.right.left = new RedBlackBST().new Node(11, "Value 11", 1, true);

        StdOut.println("Test 4");
        StdOut.println(redBlackBSTCertification.is23(root4) + " Expected: false");
        StdOut.println(redBlackBSTCertification.isBalanced(root4, 2) + " Expected: false");
        StdOut.println(redBlackBSTCertification.isBST(root4, null, null) + " Expected: true");
    }

}


Question: All 2-3 trees. Write code to generate all structurally different 2-3 trees of height 2, 3, and 4. There are 2, 7, and 112 such trees, respectively. (Hint: Use a symbol table.)

Answer:
package chapter3.section3;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by Rene Argento on 01/07/17.
 */
@SuppressWarnings("unchecked")
public class Exercise34_All23Trees {
    private static Map<Integer, Integer> differentTreeStructures = new HashMap<>();

    private Set<RedBlackBST<Integer, Integer>> generateTrees(int height, Set<RedBlackBST<Integer, Integer>> heightMinus1Trees,
                                                             Set<Integer> keysInTree) {
        Set<RedBlackBST<Integer, Integer>> generatedTrees = new HashSet<>();

        int nodesInLevel = (int) Math.pow(2, height);
        int structuresToGenerate = differentTreeStructures.get(height);

        int nodesToAdd;
        // Used to change the insertion order and generate different tree structures
        int insertionsBeforeSwitchToLeftSide = 1; //always 1
        int insertionsBeforeSwitchToRightSide = 1; //starts at 1
        int insertions;

        while (structuresToGenerate > 0) {
            for (RedBlackBST<Integer, Integer> previousTree : heightMinus1Trees) {

                RedBlackBST<Integer, Integer> currentTree = copyTree(previousTree);

                boolean addToLeftSubtree = true;
                nodesToAdd = nodesInLevel;
                insertions = 0;

                while (nodesToAdd > 0) {
                    addKeyToTree(currentTree, addToLeftSubtree, keysInTree);
                    nodesToAdd--;
                    insertions++;

                    if ((addToLeftSubtree && insertions == insertionsBeforeSwitchToRightSide)
                            || (!addToLeftSubtree && insertions == insertionsBeforeSwitchToLeftSide)) {
                        addToLeftSubtree = !addToLeftSubtree;
                        insertions = 0;
                    }
                }

                generatedTrees.add(currentTree);
                structuresToGenerate--;
                if (structuresToGenerate == 0) {
                    break;
                }

                insertionsBeforeSwitchToRightSide++;
            }
        }
        return generatedTrees;
    }

    private void addKeyToTree(RedBlackBST<Integer, Integer> currentTree, boolean addToLeftSubtree, Set<Integer> keysInTree) {
        int median = currentTree.select(currentTree.size() / 2);
        int lowerBound;
        int higherBound;

        if (addToLeftSubtree) {
            lowerBound = 0;
            higherBound = median;
        } else {
            lowerBound = median;
            higherBound = Integer.MAX_VALUE;
        }

        int randomKey = StdRandom.uniform(lowerBound, higherBound);

        while (keysInTree.contains(randomKey)) {
            randomKey = StdRandom.uniform(lowerBound, higherBound);
        }

        keysInTree.add(randomKey);
        currentTree.put(randomKey, randomKey);
    }

    private RedBlackBST<Integer, Integer> copyTree(RedBlackBST<Integer, Integer> tree) {
        RedBlackBST.Node root = tree.root;

        if (root == null) {
            return null;
        }

        Queue<RedBlackBST.Node> queue = new Queue<>();
        queue.enqueue(tree.root);

        RedBlackBST.Node newRoot = new RedBlackBST().new Node(root.key, root.value, root.size, root.color);
        Queue<RedBlackBST.Node> newTreeQueue = new Queue<>();
        newTreeQueue.enqueue(newRoot);

        while (!queue.isEmpty()) {
            RedBlackBST.Node current = queue.dequeue();
            RedBlackBST.Node currentNewTree = newTreeQueue.dequeue();

            if (current.left != null) {
                currentNewTree.left = new RedBlackBST().new Node(current.left.key, current.left.value,
                        current.left.size, current.left.color);

                queue.enqueue(current.left);
                newTreeQueue.enqueue(currentNewTree.left);
            }
            if (current.right != null) {
                currentNewTree.right = new RedBlackBST().new Node(current.right.key, current.right.value,
                        current.right.size, current.right.color);

                queue.enqueue(current.right);
                newTreeQueue.enqueue(currentNewTree.right);
            }
        }

        RedBlackBST<Integer, Integer> copyTree = new RedBlackBST<>();
        copyTree.root = newRoot;
        return copyTree;
    }

    public static void main(String[] args) {
        Exercise34_All23Trees all23Trees = new Exercise34_All23Trees();

        Set<RedBlackBST<Integer, Integer>> generatedTrees = new HashSet<>();
        Set<Integer> keysInTree = new HashSet<>();

        Set<RedBlackBST<Integer, Integer>> treesOfHeight1 = new HashSet<>();
        RedBlackBST<Integer, Integer> height1Tree = new RedBlackBST<>();
        for (int i = 0; i < 3; i++) {
            int randomKey = StdRandom.uniform(Integer.MAX_VALUE);
            height1Tree.put(randomKey, randomKey);
            keysInTree.add(randomKey);
        }

        treesOfHeight1.add(height1Tree);

        differentTreeStructures.put(2, 2);
        differentTreeStructures.put(3, 7);
        // According to http://algs4.cs.princeton.edu/errata/errata-printing8.php
        // there are 112 structurally different 2-3 trees of height 4
        differentTreeStructures.put(4, 112);

        // Height = 2
        Set<RedBlackBST<Integer, Integer>> treesOfHeight2 = all23Trees.generateTrees(2, treesOfHeight1, keysInTree);
        generatedTrees.addAll(treesOfHeight2);

        // Height = 3
        Set<RedBlackBST<Integer, Integer>> treesOfHeight3 = all23Trees.generateTrees(3, treesOfHeight2, keysInTree);
        generatedTrees.addAll(treesOfHeight3);

        // Height = 4
        Set<RedBlackBST<Integer, Integer>> treesOfHeight4 = all23Trees.generateTrees(4, treesOfHeight3, keysInTree);
        generatedTrees.addAll(treesOfHeight4);

        StdOut.println("Number of trees generated: " + generatedTrees.size());
    }
}