Question: Develop a MultiSET class that is like SET, but allows equal keys and thus implements a mathematical multiset.

Answer:
package chapter3.section5;

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

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 06/08/17.
 */
public class Exercise11 {

    /**
     * This tree has the following property:
     *
     * Let x be a node in the red-black binary search tree.
     * If y is a node in the left subtree of x, then y:key <= x:key.
     * If y is a node in the right subtree of x, then y:key >= x:key.
     */
    private class MultiSET<Key extends Comparable<Key>> {

        private static final boolean RED = true;
        private static final boolean BLACK = false;

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

            boolean color;
            int size;

            Node(Key key, int size, boolean color) {
                this.key = key;
                this.size = size;
                this.color = color;
            }
        }

        private Node root;

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

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

            return node.size;
        }

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

        private boolean isRed(Node node) {
            if (node == null) {
                return false;
            }

            return node.color == RED;
        }

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

            Node newRoot = node.right;

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

            newRoot.color = node.color;
            node.color = RED;

            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;

            newRoot.color = node.color;
            node.color = RED;

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

            return newRoot;
        }

        private void flipColors(Node node) {
            if (node == null || node.left == null || node.right == null) {
                return;
            }

            //The root must have opposite color of its two children
            if ((isRed(node) && !isRed(node.left) && !isRed(node.right))
                    || (!isRed(node) && isRed(node.left) && isRed(node.right))) {
                node.color = !node.color;
                node.left.color = !node.left.color;
                node.right.color = !node.right.color;
            }
        }

        public void add(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            root = add(root, key);
            root.color = BLACK;
        }

        private Node add(Node node, Key key) {
            if (node == null) {
                return new Node(key, 1, RED);
            }

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

            //If it is a duplicate key, put it on the left subtree
            //It is important to notice that since we have rotations,
            // the duplicate keys may be on the left or right subtrees later
            if (compare <= 0) {
                node.left = add(node.left, key);
            } else if (compare > 0) {
                node.right = add(node.right, key);
            }

            if (isRed(node.right) && !isRed(node.left)) {
                node = rotateLeft(node);
            }
            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }
            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

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

            Node currentNode = root;

            while (currentNode != null) {
                int compare = key.compareTo(currentNode.key);

                if (compare < 0) {
                    currentNode = currentNode.left;
                } else if (compare > 0) {
                    currentNode = currentNode.right;
                } else {
                    return true;
                }
            }

            return false;
        }

        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 rankFirst(Key key) {
            return rankFirst(root, key);
        }

        private int rankFirst(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 rankFirst(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankFirst(node.right, key);
            } else {
                boolean hasDuplicateOnLeftSubtree = false;

                if (node.left != null && max(node.left).key.compareTo(key) == 0) {
                    hasDuplicateOnLeftSubtree = true;
                }

                if (hasDuplicateOnLeftSubtree) {
                    return rankFirst(node.left, key);
                } else {
                    return size(node.left);
                }
            }
        }

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

        private int rankLast(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 rankLast(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankLast(node.right, key);
            } else {
                boolean hasDuplicateOnRightSubtree = false;

                if (node.right != null && min(node.right).key.compareTo(key) == 0) {
                    hasDuplicateOnRightSubtree = true;
                }

                if (hasDuplicateOnRightSubtree) {
                    return size(node.left) + 1 + rankLast(node.right, key);
                } else {
                    return size(node.left);
                }
            }
        }

        //In the case of duplicates, return the rank of the rightmost key
        public int rank(Key key) {
            return rankLast(key);
        }

        //O(n lg n) since we are removing all duplicate min keys
        public void deleteMin() {
            if (isEmpty()) {
                return;
            }

            Key minKey = min();

            while (contains(minKey)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = deleteMin(root);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

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

            if (!isRed(node.left) && !isRed(node.left.left)) {
                node = moveRedLeft(node);
            }

            node.left = deleteMin(node.left);
            return balance(node);
        }

        //O(n lg n) since we are removing all duplicate max keys
        public void deleteMax() {
            if (isEmpty()) {
                return;
            }

            Key maxKey = max();

            while (contains(maxKey)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = deleteMax(root);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

        private Node deleteMax(Node node) {
            if (isRed(node.left)) {
                node = rotateRight(node);
            }

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

            if (!isRed(node.right) && !isRed(node.right.left)) {
                node = moveRedRight(node);
            }

            node.right = deleteMax(node.right);
            return balance(node);
        }

        //O(n lg n) since we are removing all duplicate keys
        public void delete(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

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

            while (contains(key)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = delete(root, key);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

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

            if (key.compareTo(node.key) < 0) {
                if (!isRed(node.left) && node.left != null && !isRed(node.left.left)) {
                    node = moveRedLeft(node);
                }

                node.left = delete(node.left, key);
            } else {
                if (isRed(node.left)) {
                    node = rotateRight(node);
                }

                if (key.compareTo(node.key) == 0 && node.right == null) {
                    return null;
                }

                if (!isRed(node.right) && node.right != null && !isRed(node.right.left)) {
                    node = moveRedRight(node);
                }

                if (key.compareTo(node.key) == 0) {
                    Node aux = min(node.right);
                    node.key = aux.key;
                    node.right = deleteMin(node.right);
                } else {
                    node.right = delete(node.right, key);
                }
            }

            return balance(node);
        }

        private Node moveRedLeft(Node node) {
            //Assuming that node is red and both node.left and node.left.left are black,
            // make node.left or one of its children red
            flipColors(node);

            if (node.right != null && isRed(node.right.left)) {
                node.right = rotateRight(node.right);
                node = rotateLeft(node);
                flipColors(node);
            }

            return node;
        }

        private Node moveRedRight(Node node) {
            //Assuming that node is red and both node.right and node.right.left are black,
            // make node.right or one of its children red
            flipColors(node);

            if (node.left != null && isRed(node.left.left)) {
                node = rotateRight(node);
                flipColors(node);
            }

            return node;
        }

        private Node balance(Node node) {
            if (node == null) {
                return null;
            }

            if (isRed(node.right)) {
                node = rotateLeft(node);
            }

            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }

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

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

            return 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 rankLast(high) - rankFirst(low) + 1;
            } else {
                return rankLast(high) - rankFirst(low);
            }
        }
    }

    public static void main(String[] args) {
        Exercise11 exercise11 = new Exercise11();
        MultiSET<Integer> multiset = exercise11.new MultiSET<>();

        //Test add()
        multiset.add(0);
        multiset.add(0);
        multiset.add(0);
        multiset.add(0);

        multiset.add(5);
        multiset.add(5);

        multiset.add(8);
        multiset.add(8);

        multiset.add(20);
        multiset.add(20);
        multiset.add(20);
        multiset.add(20);

        multiset.add(21);
        multiset.add(22);
        multiset.add(23);
        multiset.add(24);

        StdOut.println("Keys() test");
        for (Integer key : multiset.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nExpected: 0 0 0 0 5 5 8 8 20 20 20 20 21 22 23 24");

        // Test size()
        StdOut.println("Keys size: " + multiset.size() + " Expected: 16");

        // Test size() with range
        StdOut.println("Keys size [0, 20]: " + multiset.size(0, 20) + " Expected: 12");

        // Test contains()
        StdOut.println("\nContains 8: " + multiset.contains(8) + " Expected: true");
        StdOut.println("Contains 9: " + multiset.contains(9) + " Expected: false");

        // Test min()
        StdOut.println("\nMin key: " + multiset.min() + " Expected: 0");

        // Test max()
        StdOut.println("Max key: " + multiset.max() + " Expected: 24");

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

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

        // Test select()
        StdOut.println("Select key of rank 3: " + multiset.select(3) + " Expected: 0");
        StdOut.println("Select key of rank 4: " + multiset.select(4) + " Expected: 5");

        // Test rank()
        // Note that the expected rank of key 8 is 7 and not 6, because we are assuming that rank returns the index
        // of the rightmost key when there are duplicates
        StdOut.println("Rank of key 8: " + multiset.rank(8) + " Expected: 7");
        StdOut.println("Rank of key 9: " + multiset.rank(9) + " Expected: 8");

        // Test delete()
        StdOut.println("\nDelete key 20");
        multiset.delete(20);

        for (Integer key : multiset.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nKeys size: " + multiset.size() + " Expected: 12");

        StdOut.println("\nDelete key 5");
        multiset.delete(5);

        for (Integer key : multiset.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nKeys size: " + multiset.size() + " Expected: 10");

        // Test deleteMin()
        StdOut.println("\nDelete min (key 0)");
        multiset.deleteMin();

        for (Integer key : multiset.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nKeys size: " + multiset.size() + " Expected: 6");

        // Test deleteMax()
        StdOut.println("\nDelete max (key 24)");
        multiset.deleteMax();

        for (Integer key : multiset.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nKeys size: " + multiset.size() + " Expected: 5");

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

        StdOut.println("\n\nKeys in range [20, 22]");
        for (Integer key : multiset.keys(20, 22)) {
            StdOut.print(key + " ");
        }

        // Delete all
        StdOut.println("\n\nDelete all");
        while (multiset.size() > 0) {
            for (Integer key : multiset.keys()) {
                StdOut.print(key + " ");
            }
            // multiset.delete(multiset.select(0));
            multiset.delete(multiset.select(multiset.size() - 1));
            StdOut.println();
        }
    }
}


Question: Modify LookupCSV to make a program RangeLookupCSV that takes two key values from the standard input and prints all key-value pairs in the .csv file such that the key falls within the range specified.

Answer:
package chapter3.section5;

import chapter3.section3.RedBlackBST;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import util.Constants;

/**
 * Created by Rene Argento on 06/08/17.
 */
public class RangeLookupCSV {

    // Parameters example: 0: csv_file.txt
    //                     1: 0
    //                     2: 1

    // Queries: arnold fzkey
    //          rachel wrong

    // Output expected:
    // arnold 200
    // dijkstra 10
    // dwayne 201
    // fenwick 202
    //
    // rene 5
    // sedgewick 9
    // wayne 10

    private void rangeLookupCSV(String[] args) {
        String filePath = Constants.FILES_PATH + args[0];

        In in = new In(filePath);
        int keyField = Integer.parseInt(args[1]);
        int valueField = Integer.parseInt(args[2]);

        RedBlackBST<String, String> symbolTable = new RedBlackBST<>();

        while (in.hasNextLine()) {
            String line = in.readLine();
            String[] tokens = line.split(",");
            String key = tokens[keyField];
            String value = tokens[valueField];

            symbolTable.put(key, value);
        }

        while (!StdIn.isEmpty()) {
            String queryKey1 = StdIn.readString();
            String queryKey2 = StdIn.readString();

            for (String key : symbolTable.keys(queryKey1, queryKey2)) {
                StdOut.println(key + " " + symbolTable.get(key));
            }
            StdOut.println();
        }
    }

    public static void main(String[] args) {
        new Exercise13().rangeLookupCSV(args);
    }
}


Question: Develop and test a static method invert() that takes as argument an ST<String, Bag<String>> and produces as return value the inverse of the given symbol table (a symbol table of the same type).

Answer:
package chapter3.section5;

import chapter1.section3.Bag;
import chapter3.section3.RedBlackBST;
import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 06/08/17.
 */
public class Exercise14 {

    private static RedBlackBST<String, Bag<String>> invert(RedBlackBST<String, Bag<String>> symbolTable) {
        RedBlackBST<String, Bag<String>> inverseSymbolTable = new RedBlackBST<>();

        for (String key : symbolTable.keys()) {
            Bag<String> values = symbolTable.get(key);

            for (String newKey : values) {
                if (!inverseSymbolTable.contains(newKey)) {
                    inverseSymbolTable.put(newKey, new Bag<>());
                }
                inverseSymbolTable.get(newKey).add(key);
            }
        }

        return inverseSymbolTable;
    }

    public static void main(String[] args) {
        RedBlackBST<String, Bag<String>> redBlackBST = new RedBlackBST<>();
        Bag<String> colorsBag = new Bag<>();
        colorsBag.add("red");
        colorsBag.add("green");
        colorsBag.add("blue");
        redBlackBST.put("Colors", colorsBag);

        Bag<String> sortsBag = new Bag<>();
        sortsBag.add("mergesort");
        sortsBag.add("quicksort");
        sortsBag.add("heapsort");
        redBlackBST.put("Sorts", sortsBag);

        Bag<String> mixedBag = new Bag<>();
        mixedBag.add("mergesort");
        mixedBag.add("blue");
        mixedBag.add("algorithms");
        redBlackBST.put("Mixed Bag", mixedBag);

        RedBlackBST<String, Bag<String>> inverseSymbolTable = Exercise14.invert(redBlackBST);
        for (String key : inverseSymbolTable.keys()) {
            StdOut.println(key);

            for (String value : inverseSymbolTable.get(key)) {
                StdOut.println("  " + value);
            }
        }

        // Test
        StdOut.println("\nTests");
        StdOut.println("\nred key");
        for (String value : inverseSymbolTable.get("red")) {
            StdOut.println(value);
        }
        StdOut.println("Expected: \nColors");

        StdOut.println("\nblue key");
        for (String value : inverseSymbolTable.get("blue")) {
            StdOut.println(value);
        }
        StdOut.println("Expected: \nMixed Bag\nColors");

        StdOut.println("\nquicksort key");
        for (String value : inverseSymbolTable.get("quicksort")) {
            StdOut.println(value);
        }
        StdOut.println("Expected: \nSorts");

        StdOut.println("\nmergesort key");
        for (String value : inverseSymbolTable.get("mergesort")) {
            StdOut.println(value);
        }
        StdOut.println("Expected: \nSorts\nMixed Bag");
    }
}


Question: Write a program that takes a string on standard input and an integer k as command-line argument and puts on standard output a sorted list of the k-grams found in the string, each followed by its index in the string.

Answer:
Use a symbol table from each k-gram to all positions where it appears; a plain `ST<String, Integer>` loses duplicate k-grams.

```java
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.RedBlackBST;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public class KGrams {
    public static void main(String[] args) {
        int k = Integer.parseInt(args[0]);
        String s = StdIn.readAll();
        RedBlackBST<String, Queue<Integer>> st = new RedBlackBST<>();

        for (int i = 0; i <= s.length() - k; i++) {
            String gram = s.substring(i, i + k);
            if (!st.contains(gram)) st.put(gram, new Queue<Integer>());
            st.get(gram).enqueue(i);
        }

        for (String gram : st.keys()) {
            for (int index : st.get(gram)) {
                StdOut.println(gram + " " + index);
            }
        }
    }
}
```


Question: Multisets. After referring to Exercises 3.5.2 and 3.5.3 and the previous exercise, develop APIs MultiHashSET and MultiSET for multisets (sets that can have equal keys) and implementations SeparateChainingMultiSET and BinarySearchMultiSET for multisets and ordered multisets, respectively.

Answer:
package chapter3.section5;

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

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 07/08/17.
 */
@SuppressWarnings("unchecked")
public class Exercise18_Multisets {

    /**
     * API for MultiHashSET
     *
     * public class MultiHashSET<Key>
     *     MultiHashSET()                         create an empty set
     *     void add(Key key)                      add key into the set
     *     void delete(Key key)                   remove all keys equal to key from the set
     *     boolean contains(Key key)              is key in the set?
     *     boolean isEmpty()                      is the set empty?
     *     int size()                             number of keys in the set
     *     Iterable<Key> keys()                   all the keys in the set
     *     String toString()                      string representation of the set
     */

    private interface MultiHashSET<Key> {
        void add(Key key);
        void delete(Key key);
        boolean contains(Key key);
        boolean isEmpty();
        int size();
        Iterable<Key> keys();
        String toString();
    }

    /**
     * API for MultiSET
     *
     * public class MultiSET<Key>
     *     MultiSET()                             create an empty set
     *     void add(Key key)                      add key into the set
     *     void delete(Key key)                   remove all keys equal to key from the set
     *     boolean contains(Key key)              is key in the set?
     *     boolean isEmpty()                      is the set empty?
     *     int size()                             number of keys in the set
     *     Key min()                              smallest key
     *     Key max()                              largest key
     *     Key floor(Key key)                     largest key less than or equal to key
     *     Key ceiling(Key key)                   smallest key greater than or equal to key
     *     int rankFirst(Key key)                 number of keys less than key (or less than the first key, in case of duplicates)
     *     int rankLast(Key key)                  number of keys less than key (or less than the last key, in case of duplicates)
     *     Key select(int k)                      key of rank k
     *     void deleteMin()                       delete all keys equal to the smallest key
     *     void deleteMax()                       delete all keys equal to the largest key
     *     int size(Key low, Key high)            number of keys in [low..high] (includes all duplicates)
     *     Iterable<Key> keys(Key low, Key high)  keys in [low..high], in sorted order
     *     Iterable<Key> keys()                   all the keys in the set, in sorted order
     *     String toString()                      string representation of the set
     */

    private interface MultiSET<Key> {
        void add(Key key);
        void delete(Key key);
        boolean contains(Key key);
        boolean isEmpty();
        int size();
        Key min();
        Key max();
        Key floor(Key key);
        Key ceiling(Key key);
        int rankFirst(Key key);
        int rankLast(Key key);
        Key select(int k);
        void deleteMin();
        void deleteMax();
        int size(Key low, Key high);
        Iterable<Key> keys(Key low, Key high);
        Iterable<Key> keys();
        String toString();
    }

    public class SeparateChainingMultiSET<Key> implements MultiHashSET<Key> {

        private class SequentialSearchSymbolTable<Key> {

            private class Node {
                Key key;
                Node next;

                public Node(Key key, Node next) {
                    this.key = key;
                    this.next = next;
                }
            }

            private Node first;
            private int size;

            public int size() {
                return size;
            }

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

            public boolean contains(Key key) {
                for (Node node = first; node != null; node = node.next) {
                    if (key.equals(node.key)) {
                        return true;
                    }
                }

                return false;
            }

            public void add(Key key) {
                first = new Node(key, first);
                size++;
            }

            public void delete(Key key) {
                if (first.key.equals(key)) {
                    first = first.next;
                    size--;
                    return;
                }

                for (Node node = first; node != null; node = node.next) {
                    if (node.next != null && node.next.key.equals(key)) {
                        node.next = node.next.next;
                        size--;
                        return;
                    }
                }
            }

            public Iterable<Key> keys() {
                Queue<Key> keys = new Queue<>();

                for (Node node = first; node != null; node = node.next) {
                    keys.enqueue(node.key);
                }

                return keys;
            }

        }

        private int averageListSize;

        private int size;
        private int keysSize;
        private SequentialSearchSymbolTable<Key>[] symbolTable;

        private static final int DEFAULT_HASH_TABLE_SIZE = 997;
        private static final int DEFAULT_AVERAGE_LIST_SIZE = 5;

        // The largest prime <= 2^i for i = 1 to 31
        // Used to distribute keys uniformly in the hash table after resizes
        // PRIMES[n] = 2^k - Ak where k is the power of 2 and Ak is the value to subtract to reach the previous prime number
        private final int[] PRIMES = {
                1, 1, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381,
                32749, 65521, 131071, 262139, 524287, 1048573, 2097143, 4194301,
                8388593, 16777213, 33554393, 67108859, 134217689, 268435399,
                536870909, 1073741789, 2147483647
        };

        // The lg of the hash table size
        // Used in combination with PRIMES[] to distribute keys uniformly in the hash function after resizes
        private int lgM;

        public SeparateChainingMultiSET() {
            this(DEFAULT_HASH_TABLE_SIZE, DEFAULT_AVERAGE_LIST_SIZE);
        }

        public SeparateChainingMultiSET(int initialSize, int averageListSize) {
            this.size = initialSize;
            this.averageListSize = averageListSize;
            symbolTable = new SequentialSearchSymbolTable[size];

            for (int i = 0; i < size; i++) {
                symbolTable[i] = new SequentialSearchSymbolTable<>();
            }

            lgM = (int) (Math.log(size) / Math.log(2));
        }

        public int size() {
            return keysSize;
        }

        public boolean isEmpty() {
            return keysSize == 0;
        }

        private int hash(Key key) {
            int hash = key.hashCode() & 0x7fffffff;

            if (lgM < 26) {
                hash = hash % PRIMES[lgM + 5];
            }
            return hash % size;
        }

        private double getLoadFactor() {
            return ((double) keysSize) / (double) size;
        }

        public boolean contains(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to contains() cannot be null");
            }
            return symbolTable[hash(key)].contains(key);
        }

        private void resize(int newSize) {
            SeparateChainingMultiSET<Key> separateChainingMultiSET = new SeparateChainingMultiSET<>(newSize, averageListSize);

            for (Key key : keys()) {
                separateChainingMultiSET.add(key);
            }
            symbolTable = separateChainingMultiSET.symbolTable;
            size = separateChainingMultiSET.size;
        }

        public void add(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            int hashIndex = hash(key);
            symbolTable[hashIndex].add(key);
            keysSize++;

            if (getLoadFactor() > averageListSize) {
                resize(size * 2);
                lgM++;
            }
        }

        public void delete(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to delete() cannot be null");
            }

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

            while (contains(key)) {
                symbolTable[hash(key)].delete(key);
                keysSize--;

                if (size > 1 && getLoadFactor() <= averageListSize / (double) 4) {
                    resize(size / 2);
                    lgM--;
                }
            }
        }

        public Iterable<Key> keys() {
            Queue<Key> keys = new Queue<>();

            for (SequentialSearchSymbolTable<Key> sequentialSearchST : symbolTable) {
                for (Key key : sequentialSearchST.keys()) {
                    keys.enqueue(key);
                }
            }
            return keys;
        }

        @Override
        public String toString() {
            if (isEmpty()) {
                return "{ }";
            }

            StringBuilder stringBuilder = new StringBuilder("{");

            boolean isFirstKey = true;
            for (Key key : keys()) {
                if (isFirstKey) {
                    isFirstKey = false;
                } else {
                    stringBuilder.append(",");
                }

                stringBuilder.append(" ").append(key);
            }

            stringBuilder.append(" }");
            return stringBuilder.toString();
        }
    }

    public class BinarySearchMultiSET<Key extends Comparable<Key>> implements MultiSET<Key> {
        private Key[] keys;
        private int size;

        private static final int DEFAULT_INITIAL_CAPACITY = 2;

        public BinarySearchMultiSET() {
            keys = (Key[]) new Comparable[DEFAULT_INITIAL_CAPACITY];
        }

        public BinarySearchMultiSET(int capacity) {
            keys = (Key[]) new Comparable[capacity];
        }

        public int size() {
            return size;
        }

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

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

            if (isEmpty()) {
                return false;
            }

            int rank = rankLast(key);
            if (rank < size && keys[rank].compareTo(key) == 0) {
                return true;
            } else {
                return false;
            }
        }

        public int rankFirst(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            int low = 0;
            int high = size - 1;

            int rankFound = -1;

            while (low <= high) {
                int middle = low + (high - low) / 2;

                int comparison = key.compareTo(keys[middle]);
                if (comparison < 0) {
                    high = middle - 1;
                } else if (comparison > 0) {
                    low = middle + 1;
                } else {
                    rankFound = middle;
                    high = middle - 1;
                }
            }

            if (rankFound != -1) {
                return rankFound;
            } else {
                return low;
            }
        }

        public int rankLast(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            int low = 0;
            int high = size - 1;

            int rankFound = -1;

            while (low <= high) {
                int middle = low + (high - low) / 2;

                int comparison = key.compareTo(keys[middle]);
                if (comparison < 0) {
                    high = middle - 1;
                } else if (comparison > 0) {
                    low = middle + 1;
                } else {
                    rankFound = middle;
                    low = middle + 1;
                }
            }

            if (rankFound != -1) {
                return rankFound;
            } else {
                return low;
            }
        }

        public void add(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (size == keys.length) {
                resize(keys.length * 2);
            }

            int rank = rankLast(key);

            for (int i = size; i > rank; i--) {
                keys[i] = keys[i - 1];
            }
            keys[rank] = key;
            size++;
        }

        public void delete(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to delete() cannot be null");
            }

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

            while (contains(key)) {
                int rank = rankLast(key);
                for (int i = rank; i < size - 1; i++) {
                    keys[i] = keys[i + 1];
                }

                keys[size - 1] = null;
                size--;

                if (size > 1 && size == keys.length / 4) {
                    resize(keys.length / 2);
                }
            }
        }

        public Key min() {
            if (isEmpty()) {
                throw new NoSuchElementException("Empty symbol table");
            }
            return keys[0];
        }

        public Key max() {
            if (isEmpty()) {
                throw new NoSuchElementException("Empty symbol table");
            }
            return keys[size - 1];
        }

        public Key select(int k) {
            if (isEmpty() || k >= size) {
                throw new IllegalArgumentException("Index " + k + " is higher than size");
            }
            return keys[k];
        }

        public Key ceiling(Key key) {
            int rank = rankLast(key);

            if (rank == size) {
                return null;
            }
            return keys[rank];
        }

        public Key floor(Key key) {
            if (contains(key)) {
                return key;
            }

            int rank = rankLast(key);

            if (rank == 0) {
                return null;
            }
            return keys[rank - 1];
        }

        public void deleteMin() {
            if (isEmpty()) {
                throw new NoSuchElementException("Multiset is empty");
            }

            Key minKey = min();

            while (contains(minKey)) {
                delete(minKey);
            }
        }

        public void deleteMax() {
            if (isEmpty()) {
                throw new NoSuchElementException("Multiset is empty");
            }

            Key maxKey = max();

            while (contains(maxKey)) {
                delete(maxKey);
            }
        }

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

            if (high.compareTo(low) < 0) {
                return 0;
            } else if (contains(high)) {
                return rankLast(high) - rankFirst(low) + 1;
            } else {
                return rankLast(high) - rankFirst(low);
            }
        }

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

            Queue<Key> queue = new Queue<>();

            for (int i = rankFirst(low); i < rankLast(high); i++) {
                queue.enqueue(keys[i]);
            }

            if (contains(high)) {
                queue.enqueue(keys[rankLast(high)]);
            }
            return queue;
        }

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

        private void resize(int newSize) {
            Key[] tempKeys = (Key[]) new Comparable[newSize];
            System.arraycopy(keys, 0, tempKeys, 0, size);
            keys = tempKeys;
        }

        @Override
        public String toString() {
            if (isEmpty()) {
                return "{ }";
            }

            StringBuilder stringBuilder = new StringBuilder("{");

            boolean isFirstKey = true;
            for (Key key : keys()) {
                if (isFirstKey) {
                    isFirstKey = false;
                } else {
                    stringBuilder.append(",");
                }

                stringBuilder.append(" ").append(key);
            }

            stringBuilder.append(" }");
            return stringBuilder.toString();
        }
    }

    public static void main(String[] args) {
        Exercise18_Multisets multisets = new Exercise18_Multisets();
        multisets.testSeparateChainingMultiSET();
        multisets.testBinarySearchMultiSET();
    }

    private void testSeparateChainingMultiSET() {
        StdOut.println("*********** SeparateChainingMultiSET tests ***********");
        MultiHashSET<Integer> separateChainingMultiSET = new SeparateChainingMultiSET<>();

        // Test isEmpty()
        StdOut.println("\nIsEmpty: " + separateChainingMultiSET.isEmpty() + " Expected: true");

        // Test add()
        separateChainingMultiSET.add(0);
        separateChainingMultiSET.add(0);
        separateChainingMultiSET.add(0);
        separateChainingMultiSET.add(0);

        separateChainingMultiSET.add(5);
        separateChainingMultiSET.add(5);

        separateChainingMultiSET.add(8);
        separateChainingMultiSET.add(8);

        separateChainingMultiSET.add(20);
        separateChainingMultiSET.add(20);
        separateChainingMultiSET.add(20);
        separateChainingMultiSET.add(20);

        separateChainingMultiSET.add(21);
        separateChainingMultiSET.add(22);
        separateChainingMultiSET.add(23);
        separateChainingMultiSET.add(24);

        // Test keys()
        StdOut.println("\nKeys() test");

        for (Integer key : separateChainingMultiSET.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nExpected: 0 0 0 0 5 5 8 8 20 20 20 20 21 22 23 24 - Not necessarily in this order");

        // Test size()
        StdOut.println("\nKeys size: " + separateChainingMultiSET.size() + " Expected: 16");

        StdOut.println("\ntoString() test: " + separateChainingMultiSET);

        // Test contains()
        StdOut.println("\nContains 0: " + separateChainingMultiSET.contains(0) + " Expected: true");
        StdOut.println("Contains 100: " + separateChainingMultiSET.contains(100) + " Expected: false");

        // Test delete()
        StdOut.println("\nDelete key 5");
        separateChainingMultiSET.delete(5);
        StdOut.println(separateChainingMultiSET);

        StdOut.println("\nDelete key 24");
        separateChainingMultiSET.delete(24);
        StdOut.println(separateChainingMultiSET);

        StdOut.println("\nDelete key 0");
        separateChainingMultiSET.delete(0);
        StdOut.println(separateChainingMultiSET);

        StdOut.println("\nKeys size: " + separateChainingMultiSET.size() + " Expected: 9");
        StdOut.println("\nIsEmpty: " + separateChainingMultiSET.isEmpty() + " Expected: false");
    }

    private void testBinarySearchMultiSET() {
        StdOut.println("\n\n*********** BinarySearchMultiSET tests ***********");
        MultiSET<Integer> binarySearchMultiSET = new BinarySearchMultiSET<>();

        // Test isEmpty()
        StdOut.println("\nIsEmpty: " + binarySearchMultiSET.isEmpty() + " Expected: true");

        // Test add()
        binarySearchMultiSET.add(0);
        binarySearchMultiSET.add(0);
        binarySearchMultiSET.add(0);
        binarySearchMultiSET.add(0);

        binarySearchMultiSET.add(5);
        binarySearchMultiSET.add(5);

        binarySearchMultiSET.add(8);
        binarySearchMultiSET.add(8);

        binarySearchMultiSET.add(20);
        binarySearchMultiSET.add(20);
        binarySearchMultiSET.add(20);
        binarySearchMultiSET.add(20);

        binarySearchMultiSET.add(21);
        binarySearchMultiSET.add(22);
        binarySearchMultiSET.add(23);
        binarySearchMultiSET.add(24);

        // Test keys()
        StdOut.println("\nKeys() test");
        for (Integer key : binarySearchMultiSET.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nExpected: 0 0 0 0 5 5 8 8 20 20 20 20 21 22 23 24");

        // Test size()
        StdOut.println("\nKeys size: " + binarySearchMultiSET.size() + " Expected: 16");

        // Test size() with range
        StdOut.println("Keys size [0, 20]: " + binarySearchMultiSET.size(0, 20) + " Expected: 12");

        // Test contains()
        StdOut.println("\nContains 8: " + binarySearchMultiSET.contains(8) + " Expected: true");
        StdOut.println("Contains 9: " + binarySearchMultiSET.contains(9) + " Expected: false");

        // Test min()
        StdOut.println("\nMin key: " + binarySearchMultiSET.min() + " Expected: 0");

        // Test max()
        StdOut.println("Max key: " + binarySearchMultiSET.max() + " Expected: 24");

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

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

        // Test select()
        StdOut.println("Select key of rank 3: " + binarySearchMultiSET.select(3) + " Expected: 0");
        StdOut.println("Select key of rank 4: " + binarySearchMultiSET.select(4) + " Expected: 5");

        // Test rank()
        StdOut.println("RankFirst of key 8: " + binarySearchMultiSET.rankFirst(8) + " Expected: 6");
        StdOut.println("RankFirst of key 9: " + binarySearchMultiSET.rankFirst(9) + " Expected: 8");
        StdOut.println("RankLast of key 9: " + binarySearchMultiSET.rankLast(9) + " Expected: 8");
        StdOut.println("RankFirst of key 20: " + binarySearchMultiSET.rankFirst(20) + " Expected: 8");
        StdOut.println("RankLast of key 20: " + binarySearchMultiSET.rankLast(20) + " Expected: 11");

        // Test delete()
        StdOut.println("\nDelete key 20");
        binarySearchMultiSET.delete(20);

        // Test toString()
        StdOut.println(binarySearchMultiSET);
        StdOut.println("\nKeys size: " + binarySearchMultiSET.size() + " Expected: 12");

        StdOut.println("\nDelete key 5");
        binarySearchMultiSET.delete(5);

        StdOut.println(binarySearchMultiSET);
        StdOut.println("\nKeys size: " + binarySearchMultiSET.size() + " Expected: 10");

        // Test deleteMin()
        StdOut.println("\nDelete min (key 0)");
        binarySearchMultiSET.deleteMin();

        StdOut.println(binarySearchMultiSET);
        StdOut.println("\nKeys size: " + binarySearchMultiSET.size() + " Expected: 6");

        // Test deleteMax()
        StdOut.println("\nDelete max (key 24)");
        binarySearchMultiSET.deleteMax();

        StdOut.println(binarySearchMultiSET);
        StdOut.println("\nKeys size: " + binarySearchMultiSET.size() + " Expected: 5");

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

        StdOut.println("\n\nKeys in range [20, 22]");
        for (Integer key : binarySearchMultiSET.keys(20, 22)) {
            StdOut.print(key + " ");
        }

        StdOut.println("\n\nIsEmpty: " + binarySearchMultiSET.isEmpty() + " Expected: false");

        // Delete all
        StdOut.println("\nDelete all");
        while (binarySearchMultiSET.size() > 0) {
            StdOut.println(binarySearchMultiSET);
            // binarySearchMultiSET.delete(binarySearchMultiSET.select(0));
            binarySearchMultiSET.delete(binarySearchMultiSET.select(binarySearchMultiSET.size() - 1));
        }
        StdOut.println(binarySearchMultiSET);
    }
}


Question: Concordance. Write an ST client Concordance that puts on standard output a concordance of the strings in the standard input stream (see page 498).

Answer:
package chapter3.section5;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Rene Argento on 08/08/17.
 */
public class Concordance {

    private class Concordance {

        private Map<String, List<Integer>> readInputAndBuildConcordance() {
            int wordIndex = 0;
            Map<String, List<Integer>> concordanceMap = new HashMap<>();

            while (StdIn.hasNextLine()) {
                String wordLine = StdIn.readLine();
                String[] words = wordLine.split(" ");

                for (String word : words) {
                    if (!concordanceMap.containsKey(word)) {
                        concordanceMap.put(word, new ArrayList<>());
                    }
                    concordanceMap.get(word).add(wordIndex);

                    wordIndex++;
                }
            }
            return concordanceMap;
        }

        private void outputConcordance(Map<String, List<Integer>> concordance) {
            for (String word : concordance.keySet()) {
                StdOut.print(word);
                boolean isFirstValue = true;

                for (Integer positionInText : concordance.get(word)) {
                    if (isFirstValue) {
                        isFirstValue = false;
                    } else {
                        StdOut.print(",");
                    }

                    StdOut.print(" " + positionInText);
                }
                StdOut.println();
            }
        }
    }

    public static void main(String[] args) {
        // Test
        //  This is a text to test a concordance.
        //  The text has many words. This is a good test.
        //
        // Expected output (not necessarily in this order)
        // This 0, 13
        // is 1, 14
        // a 2, 6, 15
        // text 3, 9
        // to 4
        // test 5
        // concordance. 7
        // The 8
        // has 10
        // many 11
        // words. 12
        // good 16
        // test. 17

        Exercise20_Concordance exercise20_concordance = new Exercise20_Concordance();
        Concordance concordance = exercise20_concordance.new Concordance();
        Map<String, List<Integer>> concordanceMap = concordance.readInputAndBuildConcordance();
        concordance.outputConcordance(concordanceMap);
    }
}


Question: Inverted concordance. Write a program InvertedConcordance that takes a concordance on standard input and puts the original string on standard output stream. Note: This computation is associated with a famous story having to do with the Dead Sea Scrolls. The team that discovered the original tablets enforced a secrecy rule that essentially resulted in their making public only a concordance. After a while, other researchers figured out how to invert the concordance, and the full text was eventually made public.

Answer:
package chapter3.section5;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Rene Argento on 08/08/17.
 */
public class InvertedConcordance {

    private class InvertedConcordance {

        int numberOfWords = 0;

        private Map<String, List<Integer>> readConcordanceFromInput() {
            Map<String, List<Integer>> concordanceMap = new HashMap<>();

            while (StdIn.hasNextLine()) {
                String concordanceLine = StdIn.readLine();
                String[] concordanceInformation = concordanceLine.split(" ");

                String key = concordanceInformation[0];
                concordanceMap.put(key, new ArrayList<>());

                for (int i = 1; i < concordanceInformation.length; i++) {
                    String noCommaValue = concordanceInformation[i];

                    if (noCommaValue.charAt(noCommaValue.length() - 1) == ',') {
                        noCommaValue = noCommaValue.substring(0, noCommaValue.length() - 1);
                    }

                    int position = Integer.parseInt(noCommaValue);
                    concordanceMap.get(key).add(position);

                    if (position > numberOfWords) {
                        numberOfWords = position;
                    }
                }
            }

            return concordanceMap;
        }

        private String buildTextFromConcordance(Map<String, List<Integer>> concordance) {
            String[] wordsInText = new String[numberOfWords + 1];

            for (String word : concordance.keySet()) {
                for (int position : concordance.get(word)) {
                    wordsInText[position] = word;
                }
            }

            StringBuilder text = new StringBuilder();
            boolean isFirstWord = true;

            for (String word : wordsInText) {
                if (isFirstWord) {
                    isFirstWord = false;
                } else {
                    text.append(" ");
                }

                text.append(word);
            }

            return text.toString();
        }

    }

    public static void main(String[] args) {
        // Test
        // This 0, 13
        // is 1, 14
        // a 2, 6, 15
        // text 3, 9
        // to 4
        // test 5
        // concordance. 7
        // The 8
        // has 10
        // many 11
        // words. 12
        // good 16
        // test. 17
        //
        // Expected output
        //  This is a text to test a concordance. The text has many words. This is a good test.

        Exercise21_InvertedConcordance exercise21_invertedConcordance = new Exercise21_InvertedConcordance();
        InvertedConcordance invertedConcordance = exercise21_invertedConcordance.new InvertedConcordance();

        Map<String, List<Integer>> concordance = invertedConcordance.readConcordanceFromInput();
        String text = invertedConcordance.buildTextFromConcordance(concordance);
        StdOut.println(text);
    }
}


Question: Sparse matrices. Develop an API and an implementation for sparse 2D matrices. Support matrix addition and matrix multiplication. Include constructors for row and column vectors.

Answer:
package chapter3.section5;

import chapter3.section4.SeparateChainingHashTable;
import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 15/08/17.
 */
public class Exercise23_SparseMatrices {

    private interface SparseMatrixAPI {
        SparseMatrix sum(SparseMatrix sparseMatrix);
        SparseMatrix dot(SparseMatrix sparseMatrix);
        void put(int row, int column, double value);
        double get(int row, int column);
        void delete(int row, int column);
    }

    public class SparseVector {

        private int dimension;
        private SeparateChainingHashTable<Integer, Double> hashTable;

        public SparseVector(int dimension) {
            hashTable = new SeparateChainingHashTable<>();
            this.dimension = dimension;
        }

        public int size() {
            return hashTable.size();
        }

        public void put(int key, double value) {
            hashTable.put(key, value);
        }

        public double get(int key) {
            if (!hashTable.contains(key)) {
                return 0;
            } else {
                return hashTable.get(key);
            }
        }

        public void delete(int key) {
            hashTable.delete(key);
        }

        public SparseVector plus(SparseVector sparseVector) {
            if (dimension != sparseVector.dimension) {
                throw new IllegalArgumentException("Sparse vector dimensions must be the same.");
            }

            SparseVector result = new SparseVector(dimension);

            // Copy values
            for (int key : hashTable.keys()) {
                result.put(key, get(key));
            }
            // Sum values
            for (int key : sparseVector.hashTable.keys()) {
                double sum = get(key) + sparseVector.get(key);

                if (sum != 0) {
                    result.put(key, sum);
                } else {
                    result.delete(key);
                }
            }

            return result;
        }

        public double dot(SparseVector sparseVector) {
            if (dimension != sparseVector.dimension) {
                throw new IllegalArgumentException("Sparse vector dimensions must be the same.");
            }

            double sum = 0;

            // Iterate over the vector with the fewest nonzeros
            if (size() <= sparseVector.size()) {
                for (int key : hashTable.keys()) {
                    if (sparseVector.hashTable.contains(key)) {
                        sum += get(key) * sparseVector.get(key);
                    }
                }
            } else {
                for (int key : sparseVector.hashTable.keys()) {
                    if (hashTable.contains(key)) {
                        sum += get(key) * sparseVector.get(key);
                    }
                }
            }

            return sum;
        }

        public double dot(double[] that) {
            double sum = 0.0;

            for (int key : hashTable.keys()) {
                sum += this.get(key) * that[key];
            }

            return sum;
        }

        public String toString() {
            StringBuilder stringBuilder = new StringBuilder();

            for (int key : hashTable.keys()) {
                stringBuilder.append("(").append(key).append(", ").append(get(key)).append(") ");
            }

            return stringBuilder.toString();
        }
    }

    public class SparseMatrix implements SparseMatrixAPI {

        private int rowSize;
        private int columnSize;
        private SparseVector[] rows;
        private SparseVector[] columns;

        SparseMatrix(SparseVector[] rows, SparseVector[] columns, int rowSize, int columnSize) {
            this.rows = rows;
            this.columns = columns;

            this.rowSize = rowSize;
            this.columnSize = columnSize;
        }

        SparseMatrix(int rowSize, int columnSize) {
            this.rowSize = rowSize;
            this.columnSize = columnSize;

            rows = new SparseVector[rowSize];
            for (int i = 0; i < rows.length; i++) {
                rows[i] = new SparseVector(columnSize);
            }

            columns = new SparseVector[columnSize];
            for (int i = 0; i < columns.length; i++) {
                columns[i] = new SparseVector(rowSize);
            }
        }

        @Override
        public double get(int row, int column) {
            if (row < 0 || row >= rowSize) {
                throw new IllegalArgumentException("Invalid row index");
            }
            if (column < 0 || column >= columnSize) {
                throw new IllegalArgumentException("Invalid column index");
            }

            return rows[row].get(column);
        }

        @Override
        public void put(int row, int column, double value) {
            if (row < 0 || row >= rowSize) {
                throw new IllegalArgumentException("Invalid row index");
            }
            if (column < 0 || column >= columnSize) {
                throw new IllegalArgumentException("Invalid column index");
            }

            if (value == 0) {
                delete(row, column);
                return;
            }

            rows[row].put(column, value);
            columns[column].put(row, value);
        }

        @Override
        public void delete(int row, int column) {
            if (row < 0 || row >= rowSize) {
                throw new IllegalArgumentException("Invalid row index");
            }
            if (column < 0 || column >= columnSize) {
                throw new IllegalArgumentException("Invalid column index");
            }

            rows[row].delete(column);
            columns[column].delete(row);
        }

        @Override
        public SparseMatrix sum(SparseMatrix sparseMatrix) {
            if (rowSize != sparseMatrix.rowSize || columnSize != sparseMatrix.columnSize) {
                throw new IllegalArgumentException("Matrix A rows and columns number and Matrix B rows and columns " +
                        "number must match");
            }

            SparseMatrix result = new SparseMatrix(rowSize, columnSize);

            for (int i = 0; i < result.rowSize; i++) {
                result.rows[i] = rows[i].plus(sparseMatrix.rows[i]);
                for (int column : result.rows[i].hashTable.keys()) {
                    result.columns[column].put(i, result.rows[i].get(column));
                }
            }

            return result;
        }

        @Override
        public SparseMatrix dot(SparseMatrix sparseMatrix) {
            if (columnSize != sparseMatrix.rowSize) {
                throw new IllegalArgumentException("Matrix A columns number and Matrix B rows number must match");
            }

            SparseMatrix result = new SparseMatrix(rowSize, sparseMatrix.columnSize);

            for (int i = 0; i < rows.length; i++) {
                for (int j = 0; j < sparseMatrix.columnSize; j++) {
                    double dot = rows[i].dot(sparseMatrix.columns[j]);

                    if (dot != 0) {
                        result.put(i, j, dot);
                    }
                }
            }

            return result;
        }

        public String toString() {
            StringBuilder stringBuilder = new StringBuilder("rows = " + rowSize + ", columns = " + columnSize + "\n");

            for (int row = 0; row < rowSize; row++) {
                stringBuilder.append(row).append(": ").append(rows[row]).append("\n");
            }
            return stringBuilder.toString();
        }
    }

    public static void main(String[] args) {
        Exercise23_SparseMatrices sparseMatrices = new Exercise23_SparseMatrices();

        //Matrix A
        // 1 0
        // 7 2

        //Matrix B
        // -4 -5
        //  2  1

        //Matrix C
        // 3 4 2
        // 1 0 3

        //Matrix D
        // 0 0 0
        // 0 2 0

        //Matrix A + Matrix B
        // -3 -5
        //  9  3

        //Matrix A x Matrix C
        //  3  4  2
        // 23 28 20

        //Matrix B x Matrix C
        // -17 -16 -23
        //   7   8   7

        //Matrix C + Matrix D
        // 3 4 2
        // 1 2 3

        //Matrix A x Matrix D
        // 0 0 0
        // 0 4 0

        //Matrix B x Matrix D
        // 0 -10 0
        // 0   2 0

        SparseMatrix sparseMatrixA = sparseMatrices.new SparseMatrix(2, 2);
        SparseMatrix sparseMatrixB = sparseMatrices.new SparseMatrix(2, 2);
        SparseMatrix sparseMatrixC = sparseMatrices.new SparseMatrix(2, 3);
        SparseMatrix sparseMatrixD = sparseMatrices.new SparseMatrix(2, 3);

        sparseMatrixA.put(0, 0, 1);
        sparseMatrixA.put(1, 0, 7);
        sparseMatrixA.put(1, 1, 2);

        sparseMatrixB.put(0, 0, -4);
        sparseMatrixB.put(0, 1, -5);
        sparseMatrixB.put(1, 0, 2);
        sparseMatrixB.put(1, 1, 1);

        sparseMatrixC.put(0, 0, 3);
        sparseMatrixC.put(0, 1, 4);
        sparseMatrixC.put(0, 2, 2);
        sparseMatrixC.put(1, 0, 1);
        sparseMatrixC.put(1, 2, 3);

        sparseMatrixD.put(1, 1, 2);

        StdOut.println("Matrix A + Matrix B");
        SparseMatrix sparseMatrixAPlusB = sparseMatrixA.sum(sparseMatrixB);
        StdOut.println(sparseMatrixAPlusB);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 2\n" +
                "0: (0, -3.0) (1, -5.0) \n" +
                "1: (0, 9.0) (1, 3.0) \n");

        StdOut.println("Matrix A x Matrix C");
        SparseMatrix sparseMatrixADotC = sparseMatrixA.dot(sparseMatrixC);
        StdOut.println(sparseMatrixADotC);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 3\n" +
                "0: (0, 3.0) (1, 4.0) (2, 2.0) \n" +
                "1: (0, 23.0) (1, 28.0) (2, 20.0) \n");

        StdOut.println("Matrix B x Matrix C");
        SparseMatrix sparseMatrixBDotC = sparseMatrixB.dot(sparseMatrixC);
        StdOut.println(sparseMatrixBDotC);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 3\n" +
                "0: (0, -17.0) (1, -16.0) (2, -23.0) \n" +
                "1: (0, 7.0) (1, 8.0) (2, 7.0) \n");

        StdOut.println("Matrix C + Matrix D");
        SparseMatrix sparseMatrixCPlusD = sparseMatrixC.sum(sparseMatrixD);
        StdOut.println(sparseMatrixCPlusD);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 3\n" +
                "0: (0, 3.0) (1, 4.0) (2, 2.0) \n" +
                "1: (0, 1.0) (1, 2.0) (2, 3.0) \n");

        StdOut.println("Matrix A x Matrix D");
        SparseMatrix sparseMatrixADotD = sparseMatrixA.dot(sparseMatrixD);
        StdOut.println(sparseMatrixADotD);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 3\n" +
                "0: \n" +
                "1: (1, 4.0) \n");

        StdOut.println("Matrix B x Matrix D");
        SparseMatrix sparseMatrixBDotD = sparseMatrixB.dot(sparseMatrixD);
        StdOut.println(sparseMatrixBDotD);
        StdOut.println("Expected:\n" +
                "rows = 2, columns = 3\n" +
                "0: (1, -10.0) \n" +
                "1: (1, 2.0)");
    }
}


Question:
List. Develop an implementation of the following API:
public class List<Item> implements Iterable<Item>
 List() create a list
 void addFront(Item item) add item to the front
 void addBack(Item item) add item to the back
 Item deleteFront() remove from the front
 Item deleteBack() remove from the back
 void delete(Item item) remove item from the list
 void add(int i, Item item) add item as the ith in the list
 Item delete(int i) remove the ith item from the list
 boolean contains(Item item) is key in the list?
 boolean isEmpty() is the list empty?
 int size() number of items in the list
Hint: Use two symbol tables, one to find the ith item in the list efficiently, and the other to efficiently search by item. (Java’s java.util.List interface contains methods like these but does not supply any implementation that efficiently supports all operations.)

Answer:
package chapter3.section5;

import chapter3.section3.RedBlackBST;
import chapter3.section4.SeparateChainingHashTable;
import edu.princeton.cs.algs4.StdOut;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

/**
 * Created by Rene Argento on 17/08/17.
 */
public class Exercise27_List {

    public class List<Item> implements Iterable<Item> {

        private RedBlackBST<Double, Item> itemsBST;
        private SeparateChainingHashTable<Item, Double> itemsPositions;

        private static final double INITIAL_VALUE = 50000;
        private static final double OFFSET = 0.0001;;

        List() {
            itemsBST = new RedBlackBST<>();
            itemsPositions = new SeparateChainingHashTable<>();
        }

        //O(lg n)
        public void addFront(Item item) {
            if (item == null) {
                throw new IllegalArgumentException("Item cannot be null");
            }

            if (contains(item)) {
                delete(item);
            }

            double minKey;

            if (isEmpty()) {
                minKey = INITIAL_VALUE;
            } else {
                minKey = itemsBST.min();
            }

            double newMinKey = minKey - OFFSET;

            itemsBST.put(newMinKey, item);
            itemsPositions.put(item, newMinKey);
        }

        //O(lg n)
        public void addBack(Item item) {
            if (item == null) {
                throw new IllegalArgumentException("Item cannot be null");
            }

            if (contains(item)) {
                delete(item);
            }

            double maxKey;

            if (isEmpty()) {
                maxKey = INITIAL_VALUE;
            } else {
                maxKey = itemsBST.max();
            }

            double newMaxKey = maxKey + OFFSET;

            itemsBST.put(newMaxKey, item);
            itemsPositions.put(item, newMaxKey);
        }

        //O(lg n)
        public Item deleteFront() {
            if (isEmpty()) {
                return null;
            }

            Item firstItem = itemsBST.get(itemsBST.min());

            itemsBST.deleteMin();
            itemsPositions.delete(firstItem);

            return firstItem;
        }

        //O(lg n)
        public Item deleteBack() {
            if (isEmpty()) {
                return null;
            }

            Item lastItem = itemsBST.get(itemsBST.max());

            itemsBST.deleteMax();
            itemsPositions.delete(lastItem);

            return lastItem;
        }

        //O(lg n)
        public void delete(Item item) {
            if (item == null) {
                throw new IllegalArgumentException("Item cannot be null");
            }

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

            double itemPosition = itemsPositions.get(item);
            itemsBST.delete(itemPosition);
            itemsPositions.delete(item);
        }

        //O(lg n)
        public void add(int index, Item item) {
            if (item == null) {
                throw new IllegalArgumentException("Item cannot be null");
            }

            if (index < 0 || index > size()
                    || (index == size() && contains(item))) {
                throw new IllegalArgumentException("Invalid index");
            }

            if (contains(item)) {
                delete(item);
            }

            double previousItemIndex = 0;
            double nextItemIndex = size() - 1;

            if (index > 0) {
                previousItemIndex = itemsBST.select(index - 1);
            } else if (index == 0) {
                previousItemIndex = itemsBST.min() - OFFSET;
            }

            if (index < size()) {
                nextItemIndex = itemsBST.select(index);
            } else if (index == size()) {
                nextItemIndex = itemsBST.max() + OFFSET;
            }

            double medianKey = (previousItemIndex + nextItemIndex) / 2;

            itemsBST.put(medianKey, item);
            itemsPositions.put(item, medianKey);
        }

        //O(lg n)
        public Item delete(int index) {
            if (index < 0 || index >= size()) {
                throw new IllegalArgumentException("Invalid index");
            }

            Item deletedItem = itemsBST.get(itemsBST.select(index));
            delete(deletedItem);
            return deletedItem;
        }

        //O(1)
        public boolean contains(Item item) {
            return itemsPositions.contains(item);
        }

        //O(1)
        public boolean isEmpty() {
            return size() == 0;
        }

        //O(1)
        public int size() {
            return itemsPositions.size();
        }

        @Override
        public Iterator<Item> iterator() {
            return new ListIterator();
        }

        //O(n)
        private class ListIterator implements Iterator<Item> {

            Queue<Double> keys;

            ListIterator() {
                keys = new LinkedList<>();

                for (Double key : itemsBST.keys()) {
                    keys.add(key);
                }
            }

            @Override
            public boolean hasNext() {
                return keys.size() > 0;
            }

            @Override
            public Item next() {
                return itemsBST.get(keys.poll());
            }
        }

    }

    public static void main(String[] args) {
        Exercise27_List exercise27_list = new Exercise27_List();
        List<Integer> list = exercise27_list.new List<>();

        StdOut.println("Add item 1 to the front of the list");
        //Test addFront() and addBack()
        list.addFront(1);
        StdOut.println("Add item 0 to the front of the list");
        list.addFront(0);
        StdOut.println("Add item 10 to the back of the list");
        list.addBack(10);
        StdOut.println("Add item 11 to the back of the list");
        list.addBack(11);

        //Test size()
        StdOut.println("\nSize: " + list.size() + " Expected: 4");

        //Test isEmpty()
        StdOut.println("isEmpty: " + list.isEmpty() + " Expected: false\n");

        //Test iterator
        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 0 1 10 11");

        //Test add()
        StdOut.println("\nAdd item 9 on index 2");
        list.add(2, 9);

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 0 1 9 10 11");

        StdOut.println("\nAdd item -1 on index 0");
        list.add(0, -1);

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: -1 0 1 9 10 11");

        //Test deleteFront()
        StdOut.println("\nDelete front");
        list.deleteFront();

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 0 1 9 10 11");

        //Test deleteBack()
        StdOut.println("\nDelete back");
        list.deleteBack();

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 0 1 9 10");

        //Test delete(int index)
        StdOut.println("\nDelete item on index 2");
        list.delete(2);

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 0 1 10");

        StdOut.println("\nDelete item on index 0");
        list.delete(0);

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 1 10");

        //Test delete(Item item)
        StdOut.println("\nDelete item 5");
        list.delete(new Integer(5));

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 1 10");

        StdOut.println("\nDelete item 10");
        list.delete(new Integer(10));

        for (int item : list) {
            StdOut.print(item + " ");
        }
        StdOut.println("\nExpected: 1");

        StdOut.println("\nDelete front");
        list.deleteFront();
        StdOut.println("isEmpty: " + list.isEmpty() + " Expected: true");
    }
}


Question: What is the maximum number of edges in a graph with V vertices and no parallel edges? What is the minimum number of edges in a graph with V vertices, none of which are isolated?

Answer:
The maximum number of edges in a simple undirected graph with `V` vertices is

`V(V - 1) / 2`.

If no vertex may be isolated, the minimum number of edges is `ceil(V / 2)`, not `V - 1`: pair up vertices with one edge per pair, and when `V` is odd use two edges to connect the last three vertices as a path.


Question: Draw, in the style of the figure in the text (page 524), the adjacency lists built by Graph’s input stream constructor for the file tinyGex2.txt depicted at left.

Answer:
4.1.2

adj[]
  0  -> 5 -> 2 -> 6
  1  -> 4 -> 8 -> 11
  2  -> 5 -> 6 -> 0 -> 3
  3  -> 10 -> 6 -> 2
  4  -> 1 -> 8
  5  -> 0 -> 10 -> 2
  6  -> 2 -> 3 -> 0
  7  -> 8 -> 11
  8  -> 1 -> 11 -> 7 -> 4
  9  ->
 10  -> 5 -> 3
 11  -> 8 -> 7 -> 1


Question: Consider the four-vertex graph with edges 0-1, 1-2, 2-3, and 3-0. Draw an array of adjacency-lists that could not have been built calling addEdge() for these edges no matter what order.

Answer:
4.1.6

The edges form a cycle, so changing the connection order of one of the vertices' adjacency list creates an impossible sequence of connections.

adj[]
  0  -> 1 -> 3 (the original was 0 -> 3 -> 1)
  1  -> 2 -> 0
  2  -> 3 -> 1
  3  -> 0 -> 2


Question: Show, in the style of the figure on page 533, a detailed trace of the call dfs(0) for the graph built by Graph’s input stream constructor for the file tinyGex2.txt (see Exercise 4.1.2). Also, draw the tree represented by edgeTo[].

Answer:
4.1.9
                marked[]    adj[]
dfs (0)          0 T        0 5 2 6
                 1          1 4 8 11
                 2          2 5 6 0 3
                 3          3 10 6 2
                 4          4 1 8
                 5          5 0 10 2
                 6          6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10         10 5 3
                11         11 8 7 1

 dfs (5)         0 T        0 5 2 6
  check 0        1          1 4 8 11
                 2          2 5 6 0 3
                 3          3 10 6 2
                 4          4 1 8
                 5 T        5 0 10 2
                 6          6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10         10 5 3
                11         11 8 7 1

  dfs (10)       0 T        0 5 2 6
   check 5       1          1 4 8 11
                 2          2 5 6 0 3
                 3          3 10 6 2
                 4          4 1 8
                 5 T        5 0 10 2
                 6          6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10 T       10 5 3
                11         11 8 7 1

   dfs (3)       0 T        0 5 2 6
    check 10     1          1 4 8 11
                 2          2 5 6 0 3
                 3 T        3 10 6 2
                 4          4 1 8
                 5 T        5 0 10 2
                 6          6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10 T       10 5 3
                11         11 8 7 1

    dfs (6)      0 T        0 5 2 6
                 1          1 4 8 11
                 2          2 5 6 0 3
                 3 T        3 10 6 2
                 4          4 1 8
                 5 T        5 0 10 2
                 6 T        6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10 T       10 5 3
                11         11 8 7 1

     dfs (2)     0 T        0 5 2 6
      check 5    1          1 4 8 11
      check 6    2 T        2 5 6 0 3
      check 0    3 T        3 10 6 2
      check 3    4          4 1 8
     2 done      5 T        5 0 10 2
                 6 T        6 2 3 0
                 7          7 8 11
                 8          8 1 11 7 4
                 9          9
                10 T       10 5 3
                11         11 8 7 1
     check 3
     check 0
    6 done
    check 2
   3 done
  10 done
  check 2
 5 done
 check 2
 check 6
0 done

edgeTo[] tree

           0
          5
        10
       3
      6
     2


Question: Prove that every connected graph has a vertex whose removal (including all adjacent edges) will not disconnect the graph, and write a DFS method that finds such a vertex. Hint: Consider a vertex whose adjacent vertices are all marked.

Answer:
Every connected graph has at least one vertex whose removal does not disconnect the graph: take any DFS tree and choose a leaf of that DFS tree. Removing a leaf from a tree leaves the tree connected on the remaining vertices; since the original graph contains at least the DFS-tree edges, the original graph with that leaf removed is also connected.

A DFS method can record the last vertex discovered. The last discovered vertex has no unmarked adjacent vertex when its call finishes, so it is a DFS-tree leaf and can be removed.

```java
private boolean[] marked;
private int removable;

public int removableVertex(Graph g) {
    marked = new boolean[g.V()];
    removable = 0;
    dfs(g, 0);
    return removable;
}

private void dfs(Graph g, int v) {
    marked[v] = true;
    removable = v;
    for (int w : g.adj(v)) {
        if (!marked[w]) dfs(g, w);
    }
}
```


Question: Draw the tree represented by edgeTo[] after the call bfs(G, 0) in ALGORITHM 4.2 for the graph built by Graph’s input stream constructor for the file tinyGex2.txt (see Exercise 4.1.2).

Answer:
4.1.11

Tree represented by edgeTo[] after call to bfs(G, 0):

         0
     5   2   6
  10    3


Question: What does the BFS tree tell us about the distance from v to w when neither is at the root?

Answer: The BFS tree gives exact shortest-path distances only from the root to every vertex. If neither `v` nor `w` is the root, the distance between them in the BFS tree is just the length of one particular `v-w` path, hence an upper bound on their true graph distance. The graph may contain a shorter path using edges that are not in the BFS tree.


Question: Suppose you use a stack instead of a queue when running breadth-first search. Does it still compute shortest paths?

Answer:
4.1.14

If we use a stack instead of a queue when running breadth-first search, it may not compute shortest paths.
This can be seen in the following graph:

  0 (source)
 / \
1 - 2
|   |
4 - 3

If the edge 0 - 2 is inserted before the edge 0 - 1:
Using a stack, the distance from 0 to 4 will be 3.
Using a queue, the distance from 0 to 4 will be 2.

If the edge 0 - 1 is inserted before the edge 0 - 2:
Using a stack, the distance from 0 to 3 will be 3.
Using a queue, the distance from 0 to 3 will be 2.

Thanks to lemonadeseason (https://github.com/lemonadeseason) for correcting the example in this exercise.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/24


Question: Show, in the style of the figure on page 545, a detailed trace of CC for finding the connected components in the graph built by Graph’s input stream constructor for the file tinyGex2.txt (see EXERCISE 4.1.2).

Answer:
4.1.19
                   count            marked[]                       id[]
                           0 1 2 3 4 5 6 7 8 9 10 11    0 1 2 3 4 5 6 7 8 9 10 11
dfs(0)               0     T                            0
 dfs(5)              0     T         T                  0         0
  check 0
  dfs(10)            0     T         T         T        0         0         0
   check 5
   dfs(3)            0     T     T   T         T        0     0   0         0
    check 10
    dfs(6)           0     T     T   T T       T        0     0   0 0       0
     dfs(2)          0     T   T T   T T       T        0   0 0   0 0       0
      check 5
      check 6
      check 0
      check 3
     2 done
     check 3
     check 0
    6 done
    check 2
   3 done
  10 done
  check 2
 5 done
 check 2
 check 6
0 done
dfs(1)               1     T T T T   T T       T        0 1 0 0   0 0       0
 dfs(4)              1     T T T T T T T       T        0 1 0 0 1 0 0       0
  check 1
  dfs(8)             1     T T T T T T T   T   T        0 1 0 0 1 0 0   1   0
   check 1
   dfs(11)           1     T T T T T T T   T   T  T     0 1 0 0 1 0 0   1   0  1
    check 8
    dfs(7)           1     T T T T T T T T T   T  T     0 1 0 0 1 0 0 1 1   0  1
     check 8
     check 11
    7 done
    check 1
   11 done
   check 7
   check 4
  8 done
 4 done
 check 8
 check 11
1 done
dfs(9)               2     T T T T T T T T T T T  T     0 1 0 0 1 0 0 1 1 2 0  1
9 done


Question: Show, in the style of the figures in this section, a detailed trace of Cycle for finding a cycle in the graph built by Graph’s input stream constructor for the file tinyGex2.txt (see EXERCISE 4.1.2). What is the order of growth of the running time of the Cycle constructor, in the worst case?

Answer:
4.1.20
             Has Cycle?             marked[]
                  F        0 1 2 3 4 5 6 7 8 9 10 11
dfs(0)                     T
 dfs(5)                    T         T
  check 0         F
  dfs(10)                  T         T         T
   check 5        F
   dfs(3)                  T     T   T         T
    check 10      F
    dfs(6)                 T     T   T T       T
     dfs(2)                T   T T   T T       T
      check 5     T (cycle found here)
      check 6     T
      check 0     T
      check 3     T
     2 done
     check 3      T
     check 0      T
    6 done
    check 2       T
   3 done
  10 done
  check 2         T
 5 done
 check 2          T
 check 6          T
0 done
dfs(1)                     T T T T   T T       T
 dfs(4)                    T T T T T T T       T
  check 1         T
  dfs(8)                   T T T T T T T   T   T
   check 1        T
   dfs(11)                 T T T T T T T   T   T  T
    check 8       T
    dfs(7)                 T T T T T T T T T   T  T
     check 8      T
     check 11     T
    7 done
    check 1       T
   11 done
   check 7        T
   check 4        T
  8 done
 4 done
 check 8          T
 check 11         T
1 done
dfs(9)                     T T T T T T T T T T T  T
9 done

The order of growth of the running time of the Cycle constructor, in the worst case is O(V + E).
Each adjacency-list entry is examined exactly once, and there are 2 * E such entries (two for each edge); initializing the marked[] array takes time proportional to V.


Question: Show, in the style of the figures in this section, a detailed trace of TwoColor for finding a two-coloring of the graph built by Graph’s input stream constructor for the file tinyGex2.txt (see EXERCISE 4.1.2). What is the order of growth of the running time of the TwoColor constructor, in the worst case?

Answer:
4.1.21
               Is 2-colorable?                marked[]                     color[]
                     T               0 1 2 3 4 5 6 7 8 9 10 11    0 1 2 3 4 5 6 7 8 9 10 11
dfs(0)                               T                            F F F F F F F F F F F  F
 dfs(5)                              T         T                  F F F F F T F F F F F  F
  check 0            T
  dfs(10)                            T         T         T        F F F F F T F F F F F  F
   check 5           T
   dfs(3)                            T     T   T         T        F F F T F T F F F F F  F
    check 10         T
    dfs(6)                           T     T   T T       T        F F F T F T F F F F F  F
     dfs(2)                          T   T T   T T       T        F F T T F T F F F F F  F
      check 5        F
      check 6        F
      check 0        F
      check 3        F
     2 done
     check 3         F
     check 0         F
    6 done
    check 2          F
   3 done
  10 done
  check 2            F
 5 done
 check 2             F
 check 6             F
0 done
dfs(1)                               T T T T   T T       T        F F T T F T F F F F F  F
 dfs(4)                              T T T T T T T       T        F F T T T T F F F F F  F
  check 1            F
  dfs(8)                             T T T T T T T   T   T        F F T T F T F F F F F  F
   check 1           F
   dfs(11)                           T T T T T T T   T   T  T     F F T T F T F F F F F  T
    check 8          F
    dfs(7)                           T T T T T T T T T   T  T     F F T T F T F F F F F  F
     check 8         F
     check 11        F
    7 done
    check 1          F
   11 done
   check 7           F
   check 4           F
  8 done
 4 done
 check 8             F
 check 11            F
1 done
dfs(9)                               T T T T T T T T T T T  T     F F T T F T F F F F F  F
9 done

The order of growth of the running time of the TwoColor constructor, in the worst case is O(V + E).
Each adjacency-list entry is examined exactly once, and there are 2 * E such entries (two for each edge); initializing the marked[] and color[] arrays takes time proportional to V.


Question: Compute the number of connected components in movies.txt, the size of the largest component, and the number of components of size less than 10. Find the eccentricity, diameter, radius, a center, and the girth of the largest component in the graph. Does it contain Kevin Bacon?

Answer:
4.1.24

Number of connected components: 33
Size of the largest component: 118774
Number of components of size less than 10: 5
Does the largest component contain Kevin Bacon: Yes

Eccentricity, diameter, radius, center and girth:

The strategy for computing the eccentricity, diameter, radius, center and girth of the largest component was the following:
The algorithm required for these exact computations has complexity of O(V * E) (running breadth-first search from all vertices). V is ~= 10^5, which means the algorithm required for these exact computation has complexity ~= 10^10.

In order to reduce the complexity of the computations (and to be able to compute them), domain knowledge was used to compute approximate results.
Based on the Kevin Bacon game, we know that Kevin Bacon has been on several movies and that he is closely connected to most of the actors and actresses in the graph. Therefore, he has a high probability of being the center of the graph.

Using Kevin Bacon as the center, we compute his vertex eccentricity to get the graph radius.
A breadth-first search using Kevin Bacon as the source computes the vertices that are furthest from the center. Computing the eccentricities of these vertices we can find the diameter of the graph.

The eccentricities of the center and of the vertices furthest from it are shown in the results. The range of the eccentricities  is [10, 18]. Computing the eccentricities of all vertices would bring us back to the original problem of =~ 10^10 operations.

Finally, for the girth, we know that there is a very high probability that two actors have worked together on two different movies. This gives a girth of 4, which is the minimum girth possible for the movies graph:
Actor -- Movie -- Actor
    \            /
     \   Movie  /
To validate this theory we run the algorithm to compute the girth of the graph but stop once we find a cycle of length 4, since it is the shortest cycle possible.

Eccentricities of Kevin Bacon and of vertices furthest from the center in the largest component:
Eccentricity of vertex 22970: 16
Eccentricity of vertex 22971: 16
Eccentricity of vertex 22972: 16
Eccentricity of vertex 22973: 16
Eccentricity of vertex 22974: 16
Eccentricity of vertex 22976: 16
Eccentricity of vertex 22977: 16
Eccentricity of vertex 22978: 16
Eccentricity of vertex 22979: 16
Eccentricity of vertex 22980: 16
Eccentricity of vertex 51437: 18
Eccentricity of vertex 51438: 18
Eccentricity of vertex 51439: 18
Eccentricity of vertex 51440: 18
Eccentricity of vertex 51441: 18
Eccentricity of vertex 51442: 18
Eccentricity of vertex 51443: 18
Eccentricity of vertex 51444: 18
Eccentricity of vertex 51445: 18
Eccentricity of vertex 51446: 18
Eccentricity of vertex 51447: 18
Eccentricity of vertex 51448: 18
Eccentricity of vertex 51449: 18
Eccentricity of vertex 51450: 18
Eccentricity of vertex 51451: 18
Eccentricity of vertex 51452: 18
Eccentricity of vertex 51453: 18
Eccentricity of vertex 51454: 18
Eccentricity of vertex 51455: 18
Eccentricity of vertex 51456: 18
Eccentricity of vertex 51457: 18
Eccentricity of vertex 51458: 18
Eccentricity of vertex 51459: 18
Eccentricity of vertex 51460: 18
Eccentricity of vertex 51461: 18
Eccentricity of vertex 51462: 18
Eccentricity of vertex 51463: 18
Eccentricity of vertex 51464: 18
Eccentricity of vertex 51465: 18
Eccentricity of vertex 51466: 18
Eccentricity of vertex 51467: 18
Eccentricity of vertex 51468: 18
Eccentricity of vertex 51469: 18
Eccentricity of vertex 51470: 18
Eccentricity of vertex 51471: 18
Eccentricity of vertex 51472: 18
Eccentricity of vertex 51473: 18
Eccentricity of vertex 51474: 18
Eccentricity of vertex 51475: 18
Eccentricity of vertex 51476: 18
Eccentricity of vertex 51477: 18
Eccentricity of vertex 51478: 18
Eccentricity of vertex 51479: 18
Eccentricity of vertex 51480: 18
Eccentricity of vertex 51481: 18
Eccentricity of vertex 51482: 18
Eccentricity of vertex 51483: 18
Eccentricity of vertex 51484: 18
Eccentricity of vertex 51485: 18
Eccentricity of vertex 51486: 18
Eccentricity of vertex 51487: 18
Eccentricity of vertex 51488: 18
Eccentricity of vertex 51489: 18
Eccentricity of vertex 51490: 18
Eccentricity of vertex 51491: 18
Eccentricity of vertex 51492: 18
Eccentricity of vertex 51493: 18
Eccentricity of vertex 51494: 18
Eccentricity of vertex 51495: 18
Eccentricity of vertex 51496: 18
Eccentricity of vertex 51497: 18
Eccentricity of vertex 51498: 18
Eccentricity of vertex 51499: 18
Eccentricity of vertex 51500: 18
Eccentricity of vertex 51501: 18
Eccentricity of vertex 51502: 18
Eccentricity of vertex 51503: 18
Eccentricity of vertex 51504: 18
Eccentricity of vertex 51505: 18
Eccentricity of vertex 51506: 18
Eccentricity of vertex 51507: 18
Eccentricity of vertex 51508: 18
Eccentricity of vertex 51509: 18
Eccentricity of vertex 51510: 18
Eccentricity of vertex 51511: 18
Eccentricity of vertex 51512: 18
Eccentricity of vertex 51513: 18
Eccentricity of vertex 51514: 18
Eccentricity of vertex 51515: 18
Eccentricity of vertex 51516: 18
Eccentricity of vertex 51517: 18
Eccentricity of vertex 51518: 18
Eccentricity of vertex 51519: 18
Eccentricity of vertex 51520: 18
Eccentricity of vertex 86241: 16
Eccentricity of vertex 86242: 16
Eccentricity of vertex 86243: 16
Eccentricity of vertex 86244: 16
Eccentricity of vertex 86245: 16
Eccentricity of vertex 86246: 16
Eccentricity of vertex 86247: 16
Eccentricity of vertex 86248: 16
Eccentricity of vertex 86249: 16
Eccentricity of vertex 86250: 16
Eccentricity of vertex 86251: 16
Eccentricity of vertex 86252: 16
Eccentricity of vertex 86253: 16
Eccentricity of vertex 86254: 16
Eccentricity of vertex 86255: 16
Eccentricity of vertex 86256: 16
Eccentricity of vertex 86257: 16
Eccentricity of vertex 86258: 16
Eccentricity of vertex 86259: 16
Eccentricity of vertex 118353: 18
Eccentricity of vertex 118354: 18
Eccentricity of vertex 118355: 18
Eccentricity of vertex 118356: 18
Eccentricity of vertex 118357: 18
Eccentricity of vertex 118358: 18
Eccentricity of vertex 118359: 18
Eccentricity of vertex 118360: 18
Eccentricity of vertex 118361: 18
Eccentricity of vertex 118362: 18
Eccentricity of vertex 118363: 18
Eccentricity of vertex 118364: 18
Eccentricity of vertex 9145: 10

Diameter of largest component: 18
Radius of largest component: 10
Center of largest component: 9145
Girth of largest component: 4


Question: Determine the amount of memory used by Graph to represent a graph with V vertices and E edges, using the memory-cost model of SECTION 1.4.

Answer:
4.1.27

Integer
* object overhead -> 16 bytes
* int value -> 4 bytes
* padding -> 4 bytes
Amount of memory needed: 16 + 4 + 4 = 24 bytes

Node
* object overhead -> 16 bytes
* extra overhead for reference to the enclosing instance -> 8 bytes
* Item reference (item) -> 8 bytes
* Node reference (next) -> 8 bytes
Amount of memory needed: 16 + 8 + 8 + 8 = 40 bytes

Bag
* object overhead -> 16 bytes
* Node reference (first) -> 8 bytes
* int value (size) -> 4 bytes
* padding -> 4 bytes
* N Nodes -> 40N bytes
* Integer (item) -> 24N bytes
Amount of memory needed: 16 + 8 + 4 + 4 + 40N + 24N = 64N + 32 bytes

Graph
* object overhead -> 16 bytes
* int value (V) -> 4 bytes
* int value (E) -> 4 bytes
* Bag<Integer>[] reference (adj) -> 8 bytes
* Bag<Integer>[] (adj)
   object overhead -> 16 bytes
   int value (length) -> 4 bytes
   padding -> 4 bytes
   Bag references -> 8V
   Bag -> 64E + 32 bytes -> There are V Bags and in total, they have 2E nodes -> 128E + 32V
Amount of memory needed: 16 + 4 + 4 + 8 + 16 + 4 + 4 + 8V + 128E + 32V = 128E + 40V + 56 bytes