Question: Points in the plane. Write three static comparators for the Point2D data type of page 77, one that compares points by their x coordinate, one that compares them by their y coordinate, and one that compares them by their distance from the origin. Write two non-static comparators for the Point2D data type, one that compares them by their distance to a specified point and one that compares them by their polar angle with respect to a specified point.

Answer:
package chapter2.section5;

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

import java.util.Arrays;
import java.util.Comparator;

/**
 * Created by Rene Argento on 15/04/17.
 */
public class Exercise25_PointsInThePlane {

    static class Point2D implements Comparable<Point2D> {

        private double x;
        private double y;

        Point2D(double x, double y) {
            if (Double.isInfinite(x) || Double.isInfinite(y)) {
                throw new IllegalArgumentException("Coordinates must be finite");
            }

            if (Double.isNaN(x) || Double.isNaN(y)) {
                throw new IllegalArgumentException("Coordinates cannot be NaN");
            }

            if (x == 0.0) {
                this.x = 0.0;  // convert -0.0 to +0.0
            } else {
                this.x = x;
            }

            if (y == 0.0) {
                this.y = 0.0;  // convert -0.0 to +0.0
            } else {
                this.y = y;
            }
        }

        public double x() {
            return x;
        }

        public double y() {
            return y;
        }

        //Returns the polar radius of this point.
        public double r() {
            return Math.sqrt(x * x + y * y);
        }

        //Returns the angle of this point in polar coordinates.
        public double theta() {
            return Math.atan2(y, x);
        }

        private double angleTo(Point2D that) {
            double distanceX = that.x - this.x;
            double distanceY = that.y - this.y;

            return Math.atan2(distanceY, distanceX);
        }

        public double distance(Point2D that) {
            double distanceX = this.x - that.x;
            double distanceY = this.y - that.y;

            return Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
        }

        public void draw() {
            StdDraw.point(x, y);
        }

        @Override
        public int compareTo(Point2D that) {
            if (this.y < that.y) {
                return -1;
            }
            if (this.y > that.y) {
                return 1;
            }
            if (this.x < that.x) {
                return -1;
            }
            if (this.x > that.x) {
                return 1;
            }
            return 0;
        }

        Comparator<Point2D> distanceFromPointComparator = new Comparator<Point2D>() {
            @Override
            public int compare(Point2D point2D1, Point2D point2D2) {
                double distanceFromThisPoint1 = distance(point2D1);
                double distanceFromThisPoint2 = distance(point2D2);

                if (distanceFromThisPoint1 < distanceFromThisPoint2) {
                    return -1;
                } else if (distanceFromThisPoint1 > distanceFromThisPoint2) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };

        Comparator<Point2D> polarAngleFromThisPointComparator = new Comparator<Point2D>() {
            @Override
            public int compare(Point2D point2D1, Point2D point2D2) {
                double distanceXPoint1 = point2D1.x - x;
                double distanceYPoint1 = point2D1.y - y;
                double distanceXPoint2 = point2D2.x - x;
                double distanceYPoint2 = point2D2.y - y;

                if (distanceYPoint1 >= 0 && distanceYPoint2 < 0) {
                    return -1;  // point2D1 is above; point2D2 is below
                } else if (distanceYPoint2 >= 0 && distanceYPoint1 < 0) {
                    return 1;  // point2D1 below; point2D2 above
                } else if (distanceYPoint1 == 0 && distanceYPoint2 == 0) {   // 3-collinear and horizontal
                    if (distanceXPoint1 >= 0 && distanceXPoint2 < 0) {
                        return -1;
                    } else if (distanceXPoint2 >= 0 && distanceXPoint1 < 0) {
                        return 1;
                    } else {
                        return  0;
                    }
                } else {
                    return -counterClockwise(Point2D.this, point2D1, point2D2); // both above or below
                }
            }
        };

        //Returns 1 if point1 → point2 → point3 is a counterclockwise turn
        public int counterClockwise(Point2D point1, Point2D point2, Point2D point3) {
            double area2 = (point2.x - point1.x) * (point3.y - point1.y) - (point2.y - point1.y) * (point3.x - point1.x);

            if (area2 < 0) {
                return -1;
            } else if (area2 > 0) {
                return 1;
            } else {
                return  0;
            }
        }
    }

    private static class XOrder implements Comparator<Point2D>{
        @Override
        public int compare(Point2D point2D1, Point2D point2D2) {
            if (point2D1.x < point2D2.x) {
                return -1;
            } else if (point2D1.x > point2D2.x) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    private static class YOrder implements Comparator<Point2D>{
        @Override
        public int compare(Point2D point2D1, Point2D point2D2) {
            if (point2D1.y < point2D2.y) {
                return -1;
            } else if (point2D1.y > point2D2.y) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    private static class DistanceFromOriginOrder implements Comparator<Point2D> {
        @Override
        public int compare(Point2D point2D1, Point2D point2D2) {
            double distanceFromOriginPoint2D1 = point2D1.distance(new Point2D(0, 0));
            double distanceFromOriginPoint2D2 = point2D2.distance(new Point2D(0, 0));

            if (distanceFromOriginPoint2D1 < distanceFromOriginPoint2D2) {
                return -1;
            } else if (distanceFromOriginPoint2D1 > distanceFromOriginPoint2D2) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    public static void main(String[] args) {
        Point2D point2D1 = new Point2D(0.2, 1.3);
        Point2D point2D2 = new Point2D(92.12, 140.82);
        Point2D point2D3 = new Point2D(20, 22.0);
        Point2D point2D4 = new Point2D(30, 0);

        Point2D[] points = {point2D1, point2D2, point2D3, point2D4};

        //X coordinate order
        StdOut.println("Order by X coordinate");
        Arrays.sort(points, new XOrder());

        for (Point2D point2D : points) {
            StdOut.print(point2D.x + " ");
        }

        StdOut.println("\nExpected: 0.2 20.0 30.0 92.12");

        //Y coordinate order
        StdOut.println("\nOrder by Y coordinate");
        Arrays.sort(points, new YOrder());

        for (Point2D point2D : points) {
            StdOut.print(point2D.y + " ");
        }

        StdOut.println("\nExpected: 0.0 1.3 22.0 140.82");

        //Distance from origin order
        StdOut.println("\nOrder by distance from origin");
        Arrays.sort(points, new DistanceFromOriginOrder());

        for (Point2D point2D : points) {
            StdOut.print(point2D.x + " ");
        }

        StdOut.println("\nExpected: 0.2 20.0 30.0 92.12");

        //Distance from specified point
        StdOut.println("\nOrder by distance from specified point");
        Arrays.sort(points, point2D2.distanceFromPointComparator);

        for (Point2D point2D : points) {
            StdOut.print(point2D.x + " ");
        }

        StdOut.println("\nExpected: 92.12 20.0 30.0 0.2");

        //Polar angle distance from specified point
        StdOut.println("\nOrder by polar angle distance from specified point");
        Arrays.sort(points, point2D2.polarAngleFromThisPointComparator);

        for (Point2D point2D : points) {
            StdOut.print(point2D.x + " ");
        }

        StdOut.println("\nExpected: 92.12 0.2 20.0 30.0");
    }
}


Question: Simple polygon. Given N points in the plane, draw a simple polygon with N points as vertices. Hint: Find the point p with the smallest y coordinate, breaking ties with the smallest x coordinate. Connect the points in increasing order of the polar angle they make with p.

Answer:
Choose the anchor `p` with minimum y-coordinate, breaking ties by x-coordinate. Sort every other point by the polar angle it makes with `p`, then connect the points in that order and finally connect the last point back to `p`.

```java
Point2D[] points = readPoints();
Arrays.sort(points, Point2D.Y_ORDER.thenComparing(Point2D.X_ORDER));
Point2D p = points[0];
Arrays.sort(points, 1, points.length, p.polarOrder());

for (int i = 0; i < points.length; i++) {
    Point2D a = points[i];
    Point2D b = points[(i + 1) % points.length];
    a.drawTo(b);
}
```

The sorted polar order around the lowest point gives a non-self-intersecting polygon for distinct points.


Question: Sorting parallel arrays. When sorting parallel arrays, it is useful to have a version of a sorting routine that returns a permutation, say index[], of the indices in sorted order. Add a method indirectSort() to Insertion that takes an array of Comparable objects a[] as argument, but instead of rearranging the entries of a[] returns an integer array index[] so that a[index[0]] through a[index[N-1]] are the items in ascending order.

Answer:
A direct insertion-sort version keeps the input array fixed and sorts an index array instead.

```java
public class Insertion {
    public static int[] indirectSort(Comparable[] a) {
        int n = a.length;
        int[] index = new int[n];

        for (int i = 0; i < n; i++) {
            index[i] = i;
        }

        for (int i = 1; i < n; i++) {
            int currentIndex = index[i];
            int j = i;

            while (j > 0 && less(a[currentIndex], a[index[j - 1]])) {
                index[j] = index[j - 1];
                j--;
            }
            index[j] = currentIndex;
        }

        return index;
    }

    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
}
```

The returned array is a permutation of `0..N-1`. For every adjacent pair in the returned order, `a[index[i]] <= a[index[i + 1]]`, and the original array `a[]` is never rearranged.


Question: Sort files by name. Write a program FileSorter that takes the name of a directory as a command-line argument and prints out all of the files in the current directory, sorted by file name. Hint: Use the File data type.

Answer:
package chapter2.section5;

import edu.princeton.cs.algs4.StdOut;

import java.io.File;
import java.util.Arrays;

/**
 * Created by Rene Argento on 16/04/17.
 */
public class Exercise28_SortFilesByName {

    // Parameter example: [any file path]
    public static void main(String[] args) {
        String directoryPath = args[0];
        String[] sortedFiles = new Exercise28_SortFilesByName().fileSorter(directoryPath);

        if (sortedFiles == null) {
            return;
        }

        for (String fileName : sortedFiles) {
            StdOut.println(fileName);
        }
    }

    private String[] fileSorter(String directoryPath) {
        File directory = new File(directoryPath);

        if (!directory.exists()) {
            throw new IllegalArgumentException(directoryPath + " does not exist");
        }
        if (!directory.isDirectory()) {
            throw new IllegalArgumentException(directoryPath + " is not a directory");
        }

        File[] allFiles = directory.listFiles();
        if (allFiles == null || allFiles.length == 0) {
            return null;
        }

        String[] fileNames = new String[allFiles.length];
        for (int i = 0; i < fileNames.length; i++) {
            fileNames[i] = allFiles[i].getName();
        }

        Arrays.sort(fileNames);
        return fileNames;
    }
}


Question: Sort files by size and date of last modification. Write comparators for the type File to order by increasing/decreasing order of file size, ascending/descending order of file name, and ascending/descending order of last modification date. Use these comparators in a program LS that takes a command-line argument and lists the files in the current directory according to a specified order, e.g., "-t" to sort by timestamp. Support multiple flags to break ties. Be sure to use a stable sort.

Answer:
package chapter2.section5;

import edu.princeton.cs.algs4.StdOut;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * Created by Rene Argento on 16/04/17.
 */
// Thanks to ckwastra (https://github.com/ckwastra) for suggesting a way to sort the files only once.
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/269
public class Exercise29_SortFilesBySizeAndDate {

    public static void main(String[] args) {
        Path currentRelativePath = Paths.get("");
        String path = currentRelativePath.toAbsolutePath().toString();

        File directory = new File(path);

        //Input example: -s -d -n -t
        File[] sortedFiles = new Exercise29_SortFilesBySizeAndDate().readFlagsAndSortFiles(directory, args);
        if (sortedFiles == null) {
            return;
        }

        for (File file : sortedFiles) {
            StdOut.println(file);
        }
    }

    private static final Comparator<File> NAME_ORDER = Comparator.comparing(File::getName);
    private static final Comparator<File> SIZE_ORDER = Comparator.comparingLong(File::length);
    private static final Comparator<File> TIME_ORDER = Comparator.comparingLong(File::lastModified);
    private static final Comparator<File> NAME_REVERSED_ORDER = NAME_ORDER.reversed();
    private static final Comparator<File> SIZE_REVERSED_ORDER = SIZE_ORDER.reversed();
    private static final Comparator<File> TIME_REVERSED_ORDER = TIME_ORDER.reversed();

    private File[] readFlagsAndSortFiles(File directory, String[] flags) {
        File[] filesInCurrentDirectory = directory.listFiles();
        if (filesInCurrentDirectory == null) {
            return null;
        }

        List<File> allFiles = new ArrayList<>();
        for (File file : filesInCurrentDirectory) {
            if (!file.isDirectory()) {
                allFiles.add(file);
            }
        }

        File[] files = new File[allFiles.size()];
        allFiles.toArray(files);

        /**
         * Flags:
         * -s size
         * -t timestamp
         * -n name
         *
         * -sd size decreasing
         * -td timestamp decreasing
         * -nd name decreasing
         *
         * Usage example:
         * -s -td -n
         * First sort by file size in increasing order, then by timestamp in decreasing order and
         * then by file name in increasing order
         */
        switch (flags[0]) {
            // No duplicate file names
            case "-n":
                // Sort by file name in increasing order
                Arrays.sort(files, NAME_ORDER);
                break;
            case "-nd":
                // Sort by file name in decreasing order
                Arrays.sort(files, NAME_REVERSED_ORDER);
                break;
            default:
                Comparator<File> comparator;
                switch (flags[0]) {
                    case "-t":
                        // Sort by timestamp in increasing order
                        comparator = TIME_ORDER;
                        break;
                    case "-td":
                        // Sort by timestamp in decreasing order
                        comparator = TIME_REVERSED_ORDER;
                        break;
                    case "-s":
                        // Sort by file size in increasing order
                        comparator = SIZE_ORDER;
                        break;
                    case "-sd":
                        // Sort by file size in decreasing order
                        comparator = SIZE_REVERSED_ORDER;
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid flag " + flags[0]);
                }
                // Accept up to 3 flags
                int flagsToProcess = Math.min(3, flags.length);
                outer: for (int i = 1; i < flagsToProcess; i++) {
                    switch (flags[i]) {
                        case "-n":
                            comparator = comparator.thenComparing(NAME_ORDER);
                            break outer;
                        case "-nd":
                            comparator = comparator.thenComparing(NAME_REVERSED_ORDER);
                            break outer;
                        case "-t":
                            comparator = comparator.thenComparing(TIME_ORDER);
                            break;
                        case "-td":
                            comparator = comparator.thenComparing(TIME_REVERSED_ORDER);
                            break;
                        case "-s":
                            comparator = comparator.thenComparing(SIZE_ORDER);
                            break;
                        case "-sd":
                            comparator = comparator.thenComparing(SIZE_REVERSED_ORDER);
                            break;
                        default:
                            throw new IllegalArgumentException("Invalid flag " + flags[i]);
                    }
                }
                Arrays.sort(files, comparator);
        }
        return files;
    }
}


Question: Boerner’s theorem. True or false: If you sort each column of a matrix, then sort each row, the columns are still sorted. Justify your answer.

Answer:
True.

Suppose the columns are sorted before the row sort, so row `i` is componentwise no larger than row `i+1`: for every column `j`, `a[i][j] <= a[i+1][j]`. Sorting a row just replaces that row by its order statistics. If every element of one row is componentwise no larger than the corresponding element of the next row before sorting, then the kth smallest element of the first row is no larger than the kth smallest element of the next row. Otherwise the next row would have fewer than `k` elements at least as large as the kth item, contradicting the componentwise domination.

Therefore, after each row is sorted, every column still has `a[i][j] <= a[i+1][j]`, so the columns remain sorted.


Question: Give the number of calls to put() and get() issued by FrequencyCounter, as a function of the number W of words and the number D of distinct words in the input.

Answer:
3.1.6

The put() method will be called once for every word.
The get() method will be called once for every word, except on the first time a word in being inserted in the symbol table.

Calls to put() = W
Calls to get() = W - D

Thanks to faame (https://github.com/faame) for suggesting a better answer.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/221


Question: What is the average number of distinct keys that FrequencyCounter will find among N random nonnegative integers less than 1,000, for N=10, 10^2, 10^3, 10^4, 10^5, and 10^6?

Answer:
For each of the 1,000 possible keys, the probability it appears at least once in `N` independent draws is

`1 - (999/1000)^N`.

By linearity of expectation, the expected number of distinct keys is

`1000 * (1 - (999/1000)^N)`.

| N | expected distinct keys |
|---:|---:|
| 10 | 9.955 |
| 10^2 | 95.208 |
| 10^3 | 632.305 |
| 10^4 | 999.955 |
| 10^5 | approximately 1000 |
| 10^6 | approximately 1000 |


Question: What is the most frequently used word of ten letters or more in Tale of Two Cities?

Answer:
3.1.8

Most frequently used word of ten letters or more in Tale of Two Cities: Monseigneur Frequency: 47


Question: Add code to FrequencyCounter to keep track of the last call to put(). Print the last word inserted and the number of words that were processed in the input stream prior to this insertion. Run your program for tale.txt with length cutoffs 1, 8, and 10.

Answer:
// Exercise9.java
package chapter3.section1;

import edu.princeton.cs.algs4.StdOut;
import util.FileUtil;

/**
 * Created by Rene Argento on 23/04/17.
 */
public class Exercise9 {

    public static void main(String[] args) {
        String filePath = args[0];
        new Exercise9().readBookAndGetLastWordInserted(filePath);
    }

    private void readBookAndGetLastWordInserted(String filePath) {
        int[] minLengths = {1, 8, 10};

        String[] words = FileUtil.getAllStringsFromFile(filePath);

        StdOut.printf("%12s %16s %16s\n", "Length cutoff", "Last word inserted", "Words processed");
        for (int i = 0; i < minLengths.length; i++) {
            frequencyCounter(words, minLengths[i]);
        }
    }

    private void frequencyCounter(String[] words, int minLength) {
        int totalWordsProcessed = 0;
        int wordsProcessedPriorToLastInsertion = 0;
        String lastWordInserted = "";

        BinarySearchSymbolTable<String, Integer> binarySearchSymbolTable = new BinarySearchSymbolTable<>();

        for (String word : words) {
            totalWordsProcessed++;

            if (word.length() < minLength) {
                continue;
            }

            if (!binarySearchSymbolTable.contains(word)) {
                binarySearchSymbolTable.put(word, 1);
            } else {
                binarySearchSymbolTable.put(word, binarySearchSymbolTable.get(word) + 1);
            }
            lastWordInserted = word;
            wordsProcessedPriorToLastInsertion = totalWordsProcessed - 1;
        }

        String max = "";
        binarySearchSymbolTable.put(max, 0);

        for (String word : binarySearchSymbolTable.keys()) {
            if (binarySearchSymbolTable.get(word) > binarySearchSymbolTable.get(max)) {
                max = word;
            }
        }
        printResults(minLength, lastWordInserted, wordsProcessedPriorToLastInsertion);
    }

    private void printResults(int lengthCutoff, String lastWordInserted, int wordsProcessedPriorToLastInsertion) {
        StdOut.printf("%13d %18s %16d\n", lengthCutoff, lastWordInserted, wordsProcessedPriorToLastInsertion);
    }
}

Additional notes/results:
3.1.9

Length cutoff Last word inserted  Words processed
            1            known."           135937
            8          faltering           135905
           10      disfigurement           135890


Question: Give a trace of the process of inserting the keys E A S Y Q U E S T I O N into an initially empty table using SequentialSearchST. How many compares are involved?

Answer:
3.1.10

key  value  first
 E     0      E0                                         0 compares
 A     1      A1  E0                                     1 compare
 S     2      S2  A1  E0                                 2 compares
 Y     3      Y3  S2  A1  E0                             3 compares
 Q     4      Q4  Y3  S2  A1  E0                         4 compares
 U     5      U5  Q4  Y3  S2  A1  E0                     5 compares
 E     6      U5  Q4  Y3  S2  A1  E6                     6 compares
 S     7      U5  Q4  Y3  S7  A1  E6                     4 compares
 T     8      T8  U5  Q4  Y3  S7  A1  E6                 6 compares
 I     9      I9  T8  U5  Q4  Y3  S7  A1  E6             7 compares
 O    10      O10  I9  T8  U5  Q4  Y3  S7  A1  E6        8 compares
 N    11      N11  O10  I9  T8  U5  Q4  Y3  S7  A1  E6   9 compares

Total: 55 compares

Thanks to ebber22 (https://github.com/ebber22) for finding an issue with the way the nodes were being added.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/102


Question: Give a trace of the process of inserting the keys E A S Y Q U E S T I O N into an initially empty table using BinarySearchST. How many compares are involved?

Answer:
Final keys in the table are:

`A E I N O Q S T U Y`

Using the textbook `BinarySearchST.put()` implementation, the ranks/comparisons for the insertions `E A S Y Q U E S T I O N` are:

| key | compares |
|---|---:|
| E | 0 |
| A | 2 |
| S | 2 |
| Y | 2 |
| Q | 3 |
| U | 4 |
| E | 4 |
| S | 4 |
| T | 4 |
| I | 4 |
| O | 4 |
| N | 5 |

Total compares: `38`. If you count only comparisons inside `rank()` and not the final equality check in `put()`, the total is `29`.


Question: Which of the symbol-table implementations in this section would you use for an application that does 10^3 put() operations and 10^6 get() operations, randomly intermixed? Justify your answer.

Answer:
3.1.13

For an application that does 10^3 put() operations and 10^6 get() operations I would use a binary search symbol table implementation.
The application does a lot more get() than put() operations and a binary search symbol table implementation has a O(log(n)) runtime complexity for the get() operation, which is better than the O(n) runtime complexity of the sequential search symbol table implementation.


Question: Which of the symbol-table implementations in this section would you use for an application that does 10^6 put() operations and 10^3 get() operations, randomly intermixed? Justify your answer.

Answer:
3.1.14

For an application that does 10^6 put() operations and 10^3 get() operations I would use a sequential search symbol table implementation.

The worst-case runtime cost of the put() operation for a binary search symbol table is 2N while for a sequential search symbol table it is N.
For the get() operation, the worst-case runtime cost of a binary search symbol table is lg(n) while for the sequential search symbol table it is N.

Comparing the total number of operations in the worst-case:
Sequential search symbol table: 10^6 + 10^3 ~ 10^6 operations
Binary search symbol table: 2 * 10^6 + lg(10^3) ~ 2 * 10^6 operations

The average-case runtime cost of both implementations in this case is similar because most of the operations are put() and both the sequential search symbol table and the binary search symbol table implementations have an average-case cost of N for it.

So for this application the sequential search symbol table performs better than the binary search symbol table in the worst-case and both perform similarly in the average-case, making the sequential search symbol table the best choice.

Thanks to dragon-dreamer (https://github.com/dragon-dreamer) for showing that the sequential search symbol table is the best option in this question.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/119


Question: Assume that searches are 1,000 times more frequent than insertions for a BinarySearchST client. Estimate the percentage of the total time that is devoted to insertions, when the number of searches is 10^3, 10^6, and 10^9.

Answer:
3.1.15

     Searches Percentage of total time spent on insertions
         1000                                        0.00%
      1000000                                        6.10%
   1000000000                                       96.26%

The average insertion cost is N, so the total insertion cost for N keys is N^2.
The average search cost is lg N, so the total search cost for M keys is M * lg N.
According to the question, M = 1000 * N.

The total cost is then:
total cost = N^2 + M * lg N = N^2 + 1000N * lg N

The insertion percentage is:
P = (N^2) / (N^2 + 1000N * lg N)
N^2 is the higher-order element in the equation, so as N increases, the insertion percentage approaches 100%.

Thanks to faame (https://github.com/faame) for improving this answer.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/222


Question: Prove that the rank() method in BinarySearchST is correct.

Answer:
3.1.18

The rank() method in BinarySearchST always starts the search in the middle of the array if it has an odd number of elements.
If the array has an even number of elements, the rank() method starts the search on the left of the two middle elements.
After comparing the middle element with the search key, if it is smaller, the search continues on the right side of the array.
If it is bigger, the search continues on the left side of the array.
If they have the same value, the current element's index is the rank we are looking for.

This guarantees that if an element exists in the symbol table its rank will be found in the rank() method.

When the element does not exist the value of the "low" variable will have passed the value of the "high" variable, pointing to the correct rank of where the key should be. This only happens when both the element on the left of the final rank has been checked and the element on the current (final) rank has been checked. After these checks, low will be pointing to the correct rank location.

Example:

Symbol Table: 0 1 2 3 5 6
Rank of key 4 (non-existent)

1- The initial search range is [0..5]. The rank() method checks the left of the middle elements on index 2 -> value 2
2- 2 is less than 4, so the new range to search is [3..5]. The rank() method checks the middle element on index 4 -> value 5
3- 5 is more than 4, so the new range to search is [3..3]. The rank() method checks the only element left (index 3) -> value 3
4- 3 is less than 4, so the new range to search is [4..3]. Now the "low" variable is bigger than the "high" variable.
5- The rank() method returns the value of the "low" variable, 4. This is the correct rank for key 4.


Question: Complete the proof of Proposition B (show that it holds for all values of N). Hint: Start by showing that C(N) is monotonic: C(N) <= C(N+1) for all N > 0.

Answer:
3.1.20

Proposition B. Binary search in an ordered array with N keys uses no more than lg N + 1 compares for a search (successful or unsuccessful).

Proof: Let C(N) be the number of compares to search for a key in a symbol table of size N.
We have C(0) = 0, C(1) = 1, and for N > 0 we can write a recurrence relationship that directly mirrors the recursive method:
C(N) <= C(FLOOR(N / 2)) + 1

Whether the search goes to the left or to the right, the size of the subarray is no more than FLOOR(N / 2), and we can use one compare to check for equality and to choose whether to go left or right.

Let's first prove by induction that C(N) is monotonic: C(N) <= C(N + 1) for all N > 0.

It is trivial to prove that:
C(1) = 1
C(2) = 1 or 2

Thus we have:
C(1) <= C(2)

Assume for all N=[0, K]
C(N + 1) >= C(N)

Thus we have:
C(K + 1) >= C(K)

At the beginning of the binary search, low has value 0 and high has value N. Mid is computed as:
mid = low + (high - low) / 2 = high / 2

Let's use L to represent the size of the left half and R to represent the size of the right half.
The mid element has size 1.
When N = K + 1 the total size is L + 1 + R.

When N = K + 2:
mid = low + (high - low) / 2 = (high + 1) / 2 = (N + 1) / 2

When N is incremented by 1 (from K + 1 to K + 2), the mid point either remains in the same place or shifts to the right by 1.
1- If mid remains in the same place, then L remains the same, and R is incremented by 1.
Since (R + 1) <= K + 1, we have:
C(K + 2) = C(L) + 1 + C(R + 1) >= C(L) + 1 + C(R) = C(K + 1)
2- If mid shifts to the right by 1, then L is incremented by 1 and R remains the same.
Since (L + 1) <= K + 1, we have:
C(K + 2) = C(L + 1) + 1 + C(R) >= C(L) + 1 + C(R) = C(K + 1)

Therefore, given C(K + 1) >= C(K) it is also true that C(K + 2) >= C(K + 1), which proves that C(N) is monotonic.

For a general N, we have that:

C(N) <= C(N / 2) + 1 (one comparison to check equality or decide which way of the subarray to go)
C(N / 2) <= C(N / 4) + 1

Putting the value of C(N / 2) in the first equation:
C(N) <= C(N / 4) + 1 + 1

And adding all values to the first equation until C = 1:
C(N) <= C(N / 8) + 1 + 1 + 1
C(N) <= C(N / 16) + 1 + 1 + 1 + 1
C(N) <= C(N / 2^k) + 1 + 1 + 1 + 1 + ... + 1

until we get to
C(N) <= 1 + 1 + 1 + 1 + 1 + ... + 1 (even if N is not divisible by 2 there is still a compare operation)
C(N) <= k + 1

In this case, 2^k = N
k <= lg N + 1

We can also prove it using the Master theorem:

Binary search recurrence relation:
T(N) = T(N/2) + O(1)

Master theorem:
T(N) = aT(N/b) + f(N)

Here, a = 1, b = 2 and f(n) is O(1) (constant)

c = log(a base b) = log(1 base 2) = 0

We can see that this is the case 2 of the Master theorem by taking k = 0 in this equation:
O(n^c * (log n)^k)
O(n^0 * (log n)^0) = O(1) = f(n) -> This means we are in case 2 of the Master theorem

From the Case 2 of the Master Theorem we know that:
T(n) = O(n^(log a base b) * (log n)^(k + 1))
T(n) = O(n^0 * log(n)^1) = O(log n)

With binary search, we achieve a logarithmic-time search guarantee.

Reference: https://en.wikipedia.org/wiki/Master_theorem

Thanks to faame (https://github.com/faame) for adding the section proving that C(N) is monotonic.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/223


Question: Memory usage. Compare the memory usage of BinarySearchST with that of SequentialSearchST for N key-value pairs, under the assumptions described in Section 1.4. Do not count the memory for the keys and values themselves, but do count references to them. For BinarySearchST, assume that array resizing is used, so that the array is between 25 percent and 100 percent full.

Answer:
3.1.21 - Memory usage

* BinarySearchST
   object overhead -> 16 bytes
   Key[] reference (keys) -> 8 bytes
   Value[] reference (values) -> 8 bytes
   int value (size) -> 4 bytes
   padding -> 4 bytes
   Key[]
      object overhead -> 16 bytes
      int value (length) -> 4 bytes
      padding -> 4 bytes
      N Key references -> between 8N and 32N bytes (the resizing array may be 25% to 100% full)
   Value[]
      object overhead -> 16 bytes
      int value (length) -> 4 bytes
      padding -> 4 bytes
      N Value references -> between 8N and 32N bytes (the resizing array may be 25% to 100% full)
Amount of memory needed: 16 + 8 + 8 + 4 + 4 + 16 + 4 + 4 + (8N to 32N) + 16 + 4 + 4 + (8N to 32N) = (16N to 64N) + 88 bytes

* SequentialSearchST
   object overhead -> 16 bytes
   Node reference (first) -> 8 bytes
   Node
      object overhead -> 16 bytes
      extra overhead for reference to the enclosing instance -> 8 bytes
      Key reference (key) -> 8 bytes
      Value reference (value) -> 8 bytes
      Node reference (next) -> 8 bytes
            (N Node references -> 48N bytes)
   int value (size) -> 4 bytes
   padding -> 4 bytes
Amount of memory needed: 16 + 8 + (16 + 8 + 8 + 8 + 8)N + 4 + 4 = 48N + 32 bytes


Question: Analysis of binary search. Prove that the maximum number of compares used for a binary search in a table of size N is precisely the number of bits in the binary representation of N, because the operation of shifting 1 bit to the right converts the binary representation of N into the binary representation of floor(N/2).

Answer:
3.1.23 - Analysis of binary search

As the book and exercise 3.1.20 have proven, the maximum number of compares used for a binary search in a table of size N is lg N + 1.

A number N has exactly lg N + 1 bits. This is because shifting 1 bit to the right reduces the number by half (rounded down).

For example:

N   Bit representation   Number of bits   lg N + 1
1                    1                1          1
2                   10                2          2
4                  100                3          3
5                  101                3          3
9                 1001                4          4

Therefore, the maximum number of compares used for a binary search in a table of size N is precisely the number of bits in the binary representation of N.


Question: Small tables. Suppose that a BinarySearchST client has S search operations and N distinct keys. Give the order of growth of S such that the cost of building the table is the same as the cost of all the searches.

Answer:
3.1.27 - Small tables

Building the binary search symbol table requires N calls to put().
Every put() operation makes a call to rank() and does a search, an operation with order of growth O(lg N).
Assuming that we can choose the order of the keys to insert, we can create the table in a sorted order, starting with the smallest element and ending with the highest element.
By doing this, every element will be inserted at the end of the keys[] and values[] array, making the put() operation use O(lg N) for the rank() operation and O(1) for the insert (there will be no need to move keys and values to the right since the new element is the rightmost element). N inserts will have an order of growth of O(N lg N).

A search operation has the order of growth O(lg N).
Therefore, the order of growth of S should be O(N), with S search operations having an order of growth O(N lg N), making the cost of building the table the same as the cost of all searches.

If the items are inserted in random order, the put operation has an order of growth O(N): O(lg N) for the rank operation and O(N) for inserting an element. N inserts will have an order of growth of O(N^2).
In this case, the order of growth of S should be O(N^2 / lg N).

Thanks to dragon-dreamer (https://github.com/dragon-dreamer) for correcting the orders of growth of S.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/120


Question: Add to BST a method height() that computes the height of the tree. Develop two implementations: a recursive method (which takes linear time and space proportional to the height), and a method like size() that adds a field to each node in the tree (and takes linear space and constant time per query).

Answer:
package chapter3.section2;

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

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 09/05/17.
 */
public class Exercise6 {

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

        private class Node {
            private Key key;
            private Value value;

            private Node left;
            private Node right;

            private int size; //# of nodes in subtree rooted here
            private int height; //height of the subtree rooted here

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

        private Node root;

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

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

            return node.size;
        }

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

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

            return Math.max(heightRecursive(node.left), heightRecursive(node.right)) + 1;
        }

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

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

            return node.height;
        }

        public Value get(Key key) {
            return get(root, key);
        }

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

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

        public void put(Key key, Value value) {
            root = put(root, key, value);
        }

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

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

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

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

            return node;
        }

        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);
        }

        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;
                }
            }
        }

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

            return node.key;
        }

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

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

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

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

            return select(root, index).key;
        }

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

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

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

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

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

        public void deleteMin() {
            root = deleteMin(root);
        }

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

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

            node.left = deleteMin(node.left);

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

            return node;
        }

        public void deleteMax() {
            root = deleteMax(root);
        }

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

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

            node.right = deleteMax(node.right);

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

            return node;
        }

        public void delete(Key key) {
            root = delete(root, key);
        }

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

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

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

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

        public Iterable<Key> keys(Key low, Key high) {
            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 static void main(String[] args) {
        Exercise6 exercise6 = new Exercise6();

        exercise6.testRecursiveHeightMethod();
        exercise6.testNonRecursiveHeightMethod();
    }

    private void testRecursiveHeightMethod() {
        BinarySearchTree<Integer, Integer> binarySearchTree = new BinarySearchTree<>();

        StdOut.println("Recursive height method tests");
        StdOut.println("Height 1: " + binarySearchTree.heightRecursive() + " Expected: -1");

        binarySearchTree.put(0, 0);
        binarySearchTree.put(1, 1);
        binarySearchTree.put(2, 2);
        binarySearchTree.put(3, 3);

        StdOut.println("Height 2: " + binarySearchTree.heightRecursive() + " Expected: 3");

        binarySearchTree.put(-1, -1);
        binarySearchTree.put(-2, -2);

        StdOut.println("Height 3: " + binarySearchTree.heightRecursive() + " Expected: 3");

        binarySearchTree.put(-10, -10);
        binarySearchTree.put(-7, -7);

        StdOut.println("Height 4: " + binarySearchTree.heightRecursive() + " Expected: 4");

        binarySearchTree.delete(-7);
        StdOut.println("Height 5: " + binarySearchTree.heightRecursive() + " Expected: 3");

        binarySearchTree.deleteMin();
        binarySearchTree.deleteMax();
        StdOut.println("Height 6: " + binarySearchTree.heightRecursive() + " Expected: 2");
    }

    private void testNonRecursiveHeightMethod() {
        BinarySearchTree<Integer, Integer> binarySearchTree = new BinarySearchTree<>();

        StdOut.println("\nAdded-field height method tests");
        StdOut.println("Height 1: " + binarySearchTree.heightConstant() + " Expected: -1");

        binarySearchTree.put(0, 0);
        binarySearchTree.put(1, 1);
        binarySearchTree.put(2, 2);
        binarySearchTree.put(3, 3);

        StdOut.println("Height 2: " + binarySearchTree.heightConstant() + " Expected: 3");

        binarySearchTree.put(-1, -1);
        binarySearchTree.put(-2, -2);

        StdOut.println("Height 3: " + binarySearchTree.heightConstant() + " Expected: 3");

        binarySearchTree.put(-10, -10);
        binarySearchTree.put(-7, -7);

        StdOut.println("Height 4: " + binarySearchTree.heightConstant() + " Expected: 4");

        binarySearchTree.delete(-7);
        StdOut.println("Height 5: " + binarySearchTree.heightConstant() + " Expected: 3");

        binarySearchTree.deleteMin();
        binarySearchTree.deleteMax();
        StdOut.println("Height 6: " + binarySearchTree.heightConstant() + " Expected: 2");
    }

}


Question: Add to BST a recursive method avgCompares() that computes the average number of compares required by a random search hit in a given BST (the internal path length of the tree divided by its size, plus one). Develop two implementations: a recursive method (which takes linear time and space proportional to the height), and a method like size() that adds a field to each node in the tree (and takes linear space and constant time per query).

Answer:
package chapter3.section2;

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

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 16/05/17.
 */
// Thanks to ckwastra (https://github.com/ckwastra) for fixing the internal path length computation.
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/276
public class Exercise7 {

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

        private class Node {
            private Key key;
            private Value value;

            private Node left;
            private Node right;

            private int size; //# of nodes in subtree rooted here
            private int totalNumberOfComparesRequired; //number of compares required to reach all nodes in the subtree rooted here

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

        private Node root;

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

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

        public double avgComparesRecursive() {
            if (root == null) {
                return 0;
            }
            int internalPathLength = avgComparesRecursive(root);
            return internalPathLength / (double) size() + 1;
        }

        private int avgComparesRecursive(Node node) {
            if (node == null) {
                return 0;
            }
            return node.size - 1 +
                    avgComparesRecursive(node.left) +
                    avgComparesRecursive(node.right);
        }

        public double avgComparesConstant() {
            if (root == null) {
                return 0;
            }
            return totalNumberOfComparesRequired(root) / (double) size() + 1;
        }

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

        public Value get(Key key) {
            return get(root, key);
        }

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

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

        public void put(Key key, Value value) {
            root = put(root, key, value);
        }

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

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

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

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

        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);
        }

        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;
                }
            }
        }

        public Key ceiling(Key key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return null;
            }
            return node.key;
        }

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

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

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

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

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

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

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

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

        public void deleteMin() {
            root = deleteMin(root);
        }

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

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

        public void deleteMax() {
            root = deleteMax(root);
        }

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

            node.right = deleteMax(node.right);

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

        public void delete(Key key) {
            root = delete(root, key);
        }

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

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

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

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

        public Iterable<Key> keys(Key low, Key high) {
            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 static void main(String[] args) {
        Exercise7 exercise7 = new Exercise7();

        exercise7.testAvgComparesRecursive();
        exercise7.testAvgComparesNonRecursive();
    }

    private void testAvgComparesRecursive() {
        BinarySearchTree<Integer, Integer> binarySearchTree = new BinarySearchTree<>();

        StdOut.println("Recursive average number of compares method tests");
        StdOut.printf("AVG Compares 1: %.1f Expected: 0.0\n", binarySearchTree.avgComparesRecursive());

        binarySearchTree.put(0, 0);
        binarySearchTree.put(1, 1);
        binarySearchTree.put(2, 2);
        binarySearchTree.put(3, 3);

        StdOut.printf("AVG Compares 2: %.1f Expected: 2.5\n", binarySearchTree.avgComparesRecursive());

        binarySearchTree.put(-1, -1);
        binarySearchTree.put(-2, -2);

        StdOut.printf("AVG Compares 3: %.1f Expected: 2.5\n", binarySearchTree.avgComparesRecursive());

        binarySearchTree.put(-10, -10);
        binarySearchTree.put(-7, -7);

        StdOut.printf("AVG Compares 4: %.1f Expected: 3.0\n", binarySearchTree.avgComparesRecursive());

        binarySearchTree.delete(-7);
        StdOut.printf("AVG Compares 5: %.1f Expected: 2.7\n", binarySearchTree.avgComparesRecursive());

        binarySearchTree.deleteMin();
        binarySearchTree.deleteMax();
        StdOut.printf("AVG Compares 6: %.1f Expected: 2.2\n", binarySearchTree.avgComparesRecursive());
    }

    private void testAvgComparesNonRecursive() {
        BinarySearchTree<Integer, Integer> binarySearchTree = new BinarySearchTree<>();

        StdOut.println("\nAdded-field average number of compares method tests");
        StdOut.printf("AVG Compares 1: %.1f Expected: 0.0\n", binarySearchTree.avgComparesConstant());

        binarySearchTree.put(0, 0);
        binarySearchTree.put(1, 1);
        binarySearchTree.put(2, 2);
        binarySearchTree.put(3, 3);

        StdOut.printf("AVG Compares 2: %.1f Expected: 2.5\n", binarySearchTree.avgComparesConstant());

        binarySearchTree.put(-1, -1);
        binarySearchTree.put(-2, -2);

        StdOut.printf("AVG Compares 3: %.1f Expected: 2.5\n", binarySearchTree.avgComparesConstant());

        binarySearchTree.put(-10, -10);
        binarySearchTree.put(-7, -7);

        StdOut.printf("AVG Compares 4: %.1f Expected: 3.0\n", binarySearchTree.avgComparesConstant());

        binarySearchTree.delete(-7);
        StdOut.printf("AVG Compares 5: %.1f Expected: 2.7\n", binarySearchTree.avgComparesConstant());

        binarySearchTree.deleteMin();
        binarySearchTree.deleteMax();
        StdOut.printf("AVG Compares 6: %.1f Expected: 2.2\n", binarySearchTree.avgComparesConstant());
    }
}


Question: Write a static method optCompares() that takes an integer argument N and computes the number of compares required by a random search hit in an optimal (perfectly balanced) BST, where all the null links are on the same level if the number of links is a power of 2 or on one of two levels otherwise.

Answer:
package chapter3.section2;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 27/05/17.
 */
// Thanks to ckwastra (https://github.com/ckwastra) for suggesting a O(1) solution.
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/277
public class Exercise8 {

    public static void main(String[] args) {
        StdOut.printf("Test 0: %.2f Expected: 0.00\n", optCompares(0));
        StdOut.printf("Test 1: %.2f Expected: 1.00\n", optCompares(1));
        StdOut.printf("Test 2: %.2f Expected: 1.50\n", optCompares(2));
        StdOut.printf("Test 3: %.2f Expected: 1.67\n", optCompares(3));
        StdOut.printf("Test 7: %.2f Expected: 2.43\n", optCompares(7));
        StdOut.printf("Test 8: %.2f Expected: 2.63\n", optCompares(8));
        StdOut.printf("Test 15: %.2f Expected: 3.27\n", optCompares(15));
        StdOut.printf("Test 16: %.2f Expected: 3.38\n", optCompares(16));
    }

    // O(1)
    // Consider H = height of the tree.
    // For nodes on the first H levels, the total path length is:
    // SUM(from i = 1 to H - 1) i * 2^i = (H - 2) * 2^H + 2
    // The total path in the last level is:
    // (N - (2^H - 1)) * H = (N - 2^H + 1) * H
    // Adding them, we have:
    // (H - 2) * 2^H + 2 + (N - 2^H + 1) * H = (N + 1) * H - 2^(H + 1) + 2
    private static double optCompares(int n) {
        if (n == 0) {
            return 0;
        }
        int height = (int) (Math.log(n) / Math.log(2));
        return ((n + 1) * height - Math.pow(2, height + 1) + 2) / n + 1;
    }

    // O(h)
    private static double optCompares2(int n) {
        if (n == 0) {
            return 0;
        }
        int totalCompares = 0;
        int height = (int) (Math.log(n) / Math.log(2));
        int numberOfNodesBeforeLastLevel = 0;

        // Compute the compares in all levels, except the last (because the last level may not be complete)
        for (int i = 1; i <= height; i++) {
            totalCompares += i * Math.pow(2, i - 1);
            numberOfNodesBeforeLastLevel += Math.pow(2, i - 1);
        }

        // Add compares required to reach the nodes in the last level
        int nodesInLastLevel = n - numberOfNodesBeforeLastLevel;
        totalCompares += nodesInLastLevel * (height + 1);

        // Total compares is the internal path length
        return (totalCompares / (double) n);
    }
}


Question: Draw all the different BST shapes that can result when N keys are inserted into an initially empty tree, for N = 2, 3, 4, 5, and 6.

Answer:
There are Catalan-number many BST shapes: `C_2 = 2`, `C_3 = 5`, `C_4 = 14`, `C_5 = 42`, and `C_6 = 132`.

A compact exact way to draw every shape is to use the recursive notation `X(left,right)`, with `.` for an empty subtree. The following generator prints all shapes for each requested `N`:

```java
import java.util.ArrayList;
import java.util.List;

public class BSTShapes {
    private static List<String> shapes(int n) {
        List<String> result = new ArrayList<>();
        if (n == 0) {
            result.add(".");
            return result;
        }
        for (int leftSize = 0; leftSize < n; leftSize++) {
            int rightSize = n - 1 - leftSize;
            for (String left : shapes(leftSize)) {
                for (String right : shapes(rightSize)) {
                    result.add("X(" + left + "," + right + ")");
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {
        for (int n = 2; n <= 6; n++) {
            List<String> s = shapes(n);
            System.out.println("N = " + n + " count = " + s.size());
            for (String shape : s) System.out.println(shape);
        }
    }
}
```

This enumerates exactly all possible BST shapes because choosing the root leaves `k` keys on the left and `N-1-k` keys on the right, independently, for each `k` from `0` to `N-1`.


Question: Write a test client TestBST.java for use in testing the implementations of min(), max(), floor(), ceiling(), select(), rank(), delete(), deleteMin(), deleteMax(), and keys() that are given in the text. Start with the standard indexing client given on page 370. Add code to take additional command-line arguments, as appropriate.

Answer:
package chapter3.section2;

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

/**
 * Created by Rene Argento on 28/05/17.
 */
public class TestBST {

    public static void main(String[] args) {

        /** Test type
         * 0- Keys()
         * 1- Min()
         * 2- Max()
         * 3- Floor()
         * 4- Ceiling()
         * 5- Select()
         * 6- Rank()
         * 7- Delete()
         * 8- DeleteMin()
         * 9- DeleteMax()
         * 10- Keys() with range
         *
         * Or no value, for all tests
         */

        int testType = -1;
        if (args.length == 1) {
            testType = Integer.parseInt(args[0]);
        }

        // Test values:
        // 5 1 9 2 0 99

        BinarySearchTree<Integer, String> binarySearchTree = new BinarySearchTree<>();

        while (!StdIn.isEmpty()) {
            Integer key = StdIn.readInt();
            binarySearchTree.put(key, "Value " + key);
        }

        if (testType == -1 || testType == 0) {
            StdOut.println("Keys() test");

            for (Integer key : binarySearchTree.keys()) {
                StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
            }

            StdOut.println("Expected: 0 1 2 5 9 99\n");
        }

        if (testType == -1 || testType == 1) {
            // Test min()
            StdOut.println("Min key: " + binarySearchTree.min() + " Expected: 0");
        }

        if (testType == -1 || testType == 2) {
            // Test max()
            StdOut.println("Max key: " + binarySearchTree.max() + " Expected: 99");
        }

        if (testType == -1 || testType == 3) {
            // Test floor()
            StdOut.println("Floor of 5: " + binarySearchTree.floor(5) + " Expected: 5");
            StdOut.println("Floor of 15: " + binarySearchTree.floor(15) + " Expected: 9");
        }

        if (testType == -1 || testType == 4) {
            // Test ceiling()
            StdOut.println("Ceiling of 5: " + binarySearchTree.ceiling(5) + " Expected: 5");
            StdOut.println("Ceiling of 15: " + binarySearchTree.ceiling(15) + " Expected: 99");
        }

        if (testType == -1 || testType == 5) {
            // Test select()
            StdOut.println("Select key of rank 4: " + binarySearchTree.select(4) + " Expected: 9");
        }

        if (testType == -1 || testType == 6) {
            // Test rank()
            StdOut.println("Rank of key 9: " + binarySearchTree.rank(9) + " Expected: 4");
            StdOut.println("Rank of key 10: " + binarySearchTree.rank(10) + " Expected: 5");
        }

        if (testType == -1 || testType == 7) {
            // Test delete()
            StdOut.println("\nDelete key 2");
            binarySearchTree.delete(2);

            for (Integer key : binarySearchTree.keys()) {
                StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
            }
        }

        if (testType == -1 || testType == 8) {
            // Test deleteMin()
            StdOut.println("\nDelete min (key 0)");
            binarySearchTree.deleteMin();

            for (Integer key : binarySearchTree.keys()) {
                StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
            }
        }

        if (testType == -1 || testType == 9) {
            // Test deleteMax()
            StdOut.println("\nDelete max (key 99)");
            binarySearchTree.deleteMax();

            for (Integer key : binarySearchTree.keys()) {
                StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
            }
        }

        if (testType == -1 || testType == 10) {
            // Test keys() with range
            StdOut.println("\nKeys in range [2, 10]");
            for (Integer key : binarySearchTree.keys(2, 10)) {
                StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
            }
        }
    }
}


Question: How many binary tree shapes of N nodes are there with height N? How many different ways are there to insert N distinct keys into an initially empty BST that result in a tree of height N? (See Exercise 3.2.2.)

Answer:
3.2.11

We can build different shapes of trees of height N with all combinations of right and left links on nodes with children.
Example with N = 4:

Insertion order: 1 2 3 4
1
 2
  3
   4

Insertion order: 1 2 4 3
1
 2
  4
 3

Insertion order: 1 4 3 2
 1
  4
 3
2

Insertion order: 1 4 2 3
 1
  4
 2
  3

Insertion order: 4 1 3 2
 4
1
 3
2

Insertion order: 4 1 2 3
 4
1
 2
  3

Insertion order: 4 3 1 2
  4
 3
1
 2

Insertion order: 4 3 2 1
   4
  3
 2
1

Therefore, there are 2^(N - 1) binary tree shapes of N nodes with height N.
And there are 2^(N - 1) different ways to insert N distinct keys into an initially empty BST that result in a tree of height N.

Thanks to Oreshnik (https://github.com/Oreshnik) for finding the correct solution to this exercise.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/63


Question: Give nonrecursive implementations of get() and put() for BST.

Answer:
package chapter3.section2;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 30/05/17.
 */
// Thanks to faame (https://github.com/faame) for suggesting an improvement to the put() method.
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/228
public class Exercise13 {

    private class BinarySearchTree<Key extends Comparable<Key>, Value> extends chapter3.section2.BinarySearchTree<Key, Value>{

        public Value get(Key key) {
            Node current = root;

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

                if (compare < 0) {
                    current = current.left;
                } else if (compare > 0) {
                    current = current.right;
                } else {
                    return current.value;
                }
            }
            return null;
        }

        public void put(Key key, Value value) {
            // First pass to check if the key already exists
            Node current = root;

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

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

            // Second pass
            // If we reached here, the key does not exist yet

            if (root == null) {
                root = new Node(key, value, 1);
                return;
            }

            current = root;

            while (true) {
                int compare = key.compareTo(current.key);
                current.size = current.size + 1;

                if (compare < 0) {

                    if (current.left != null) {
                        current = current.left;
                    } else {
                        current.left = new Node(key, value, 1);
                        break;
                    }
                } else if (compare > 0) {

                    if (current.right != null) {
                        current = current.right;
                    } else {
                        current.right = new Node(key, value, 1);
                        break;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        Exercise13 exercise13 = new Exercise13();
        BinarySearchTree<Integer, String> binarySearchTree = exercise13.new BinarySearchTree<>();

        // Test put()
        binarySearchTree.put(5, "Value 5");
        binarySearchTree.put(1, "Value 1");
        binarySearchTree.put(9, "Value 9");
        binarySearchTree.put(2, "Value 2");
        binarySearchTree.put(0, "Value 0");
        binarySearchTree.put(99, "Value 99");

        StdOut.println();

        // Test size()
        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 6");

        // Test get() and keys()
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

        // Test delete()
        StdOut.println("\nDelete key 2");
        binarySearchTree.delete(2);
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

        StdOut.println();

        // Test size()
        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 5");

        // Test min()
        StdOut.println("Min key: " + binarySearchTree.min() + " Expected: 0");

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

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

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

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

        // Test rank()
        StdOut.println("Rank of key 9: " + binarySearchTree.rank(9) + " Expected: 3");
        StdOut.println("Rank of key 10: " + binarySearchTree.rank(10) + " Expected: 4");

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

        binarySearchTree.deleteMin();
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

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

        binarySearchTree.deleteMax();
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

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

        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 3");
    }
}


Question: Give nonrecursive implementations of min(), max(), floor(), ceiling(), rank(), and select().

Answer:
package chapter3.section2;

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

import java.util.NoSuchElementException;
import java.util.Stack;

/**
 * Created by Rene Argento on 31/05/17.
 */
public class Exercise14 {

    private class BinarySearchTree<Key extends Comparable<Key>, Value> extends chapter3.section2.BinarySearchTree<Key, Value>{

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

            Node current = root;

            while (current.left != null) {
                current = current.left;
            }

            return current.key;
        }

        //Used for the delete operation
        public Node min(Node current) {

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

            while (current.left != null) {
                current = current.left;
            }

            return current;
        }

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

            Node current = root;

            while (current.right != null) {
                current = current.right;
            }

            return current.key;
        }

        public Key floor(Key key) {

            Node current = root;
            Key currentFloor = null;

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

                if (compare < 0) {
                    current = current.left;
                } else if (compare > 0) {
                    currentFloor = current.key;
                    current = current.right;
                } else {
                    currentFloor = current.key;
                    break;
                }
            }

            return currentFloor;
        }

        public Key ceiling(Key key) {

            Node current = root;
            Key currentCeiling = null;

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

                if (compare < 0) {
                    currentCeiling = current.key;
                    current = current.left;
                } else if (compare > 0) {
                    current = current.right;
                } else {
                    currentCeiling = current.key;
                    break;
                }
            }

            return currentCeiling;
        }

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

            Node current = root;

            while (current != null) {
                int leftSubtreeSize = size(current.left);

                if (leftSubtreeSize == index) {
                    return current.key;
                } else if (leftSubtreeSize > index) {
                    current = current.left;
                } else {
                    index -= (leftSubtreeSize + 1);
                    current = current.right;
                }

            }

            return null;
        }

        public int rank(Key key) {
            Node current = root;

            int rank = 0;

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

                if (compare < 0) {
                    current = current.left;
                } else if (compare > 0) {
                    rank += size(current.left) + 1;
                    current = current.right;
                } else {
                    rank += size(current.left);
                    return rank;
                }
            }

            return rank;
        }

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

            Stack<Node> stack = new Stack<>();

            Node current = root;

            while (current != null || !stack.isEmpty()) {
                if (current != null) {
                    stack.push(current);
                    current = current.left;
                } else {
                    current = stack.pop();
                    queue.enqueue(current.key);

                    current = current.right;
                }
            }

            return queue;
        }

    }

    public static void main(String[] args) {
        Exercise14 exercise14 = new Exercise14();
        BinarySearchTree<Integer, String> binarySearchTree = exercise14.new BinarySearchTree<>();

        // Test put()
        binarySearchTree.put(5, "Value 5");
        binarySearchTree.put(1, "Value 1");
        binarySearchTree.put(9, "Value 9");
        binarySearchTree.put(2, "Value 2");
        binarySearchTree.put(0, "Value 0");
        binarySearchTree.put(99, "Value 99");

        StdOut.println();

        // Test size()
        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 6");

        // Test get() and keys()
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

        // Test delete()
        StdOut.println("\nDelete key 2");
        binarySearchTree.delete(2);
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

        StdOut.println();

        // Test size()
        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 5");

        // Test min()
        StdOut.println("Min key: " + binarySearchTree.min() + " Expected: 0");

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

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

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

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

        // Test rank()
        StdOut.println("Rank of key 9: " + binarySearchTree.rank(9) + " Expected: 3");
        StdOut.println("Rank of key 10: " + binarySearchTree.rank(10) + " Expected: 4");

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

        binarySearchTree.deleteMin();
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

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

        binarySearchTree.deleteMax();
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

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

        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 3");
    }
}


Question:
Give the sequences of nodes examined when the methods in BST are used to compute each of the following quantities for this tree:

```text
        E
       / \
      D   Q
         / \
        J   T
         \  /
          M S
```

a. floor("Q")
b. select(5)
c. ceiling("Q")
d. rank("J")
e. size("D", "T")
f. keys("D", "T")

Answer:
3.2.15

a. floor("Q") - E Q
b. select(5) - E Q
c. ceiling("Q") - E Q
d. rank("J") - E Q J
e. size("D", "T") - E D Q J M T S
f. keys("D", "T") - E D Q J M T S


Question: Draw the sequence of BSTs that results when you delete the keys from the tree of Exercise 3.2.1, one by one, in alphabetical order.

Answer:
3.2.18

Initial tree
              E
           A      S
               Q     Y
              I     U
               O   T
              N

Delete A
              E
                  S
               Q     Y
              I     U
               O   T
              N

Delete E
               S
            Q     Y
           I     U
            O   T
           N

Delete I
               S
            Q     Y
           O     U
          N     T

Delete N
               S
            Q     Y
           O     U
                T

Delete O
               S
            Q     Y
                 U
                T

Delete Q
               S
                  Y
                 U
                T

Delete S
               Y
              U
             T

Delete T
               Y
              U

Delete U
               Y

Delete Y


Question: Prove that the running time of the two-argument keys() in a BST with N nodes is at most proportional to the tree height plus the number of keys in the range.

Answer:
3.2.20

Proposition: The running time of the two-argument keys() in a BST is at most proportional to the tree height plus the number of keys in the range.

Proof: The two-argument keys() method in a BST works in the following way:
1- It searches the tree until it finds the element which is equal or higher than the lower bound.
2- It adds the element to the queue and also adds all its right children that are inside the search range.
3- It adds all the other elements which are inside the search range by doing an in-order search in the tree (but cutting branches once it finds elements outside the range).

Step 1, searching the element which is equal or higher than the lower bound is a regular search in a BST and takes O(lg N) time, which is equal, in the worst case, to the tree height. Steps 2 and 3 combined take R operations, where R is the number of elements in the range searched. There may be a constant number of other compares, which are the cases where the method finds an element outside the range and stops the search on the current branch of the tree.
Combining all the steps the two-argument keys() in a BST is at most proportional to the tree height plus the number of keys in the range.


Question: Add a BST method randomKey() that returns a random key from the symbol table in time proportional to the tree height, in the worst case.

Answer:
package chapter3.section2;

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

/**
 * Created by Rene Argento on 02/06/17.
 */
// Thanks to Daniel Bedrenko (https://github.com/dbedrenko) for correcting and suggesting a better implementation
// for this exercise: https://github.com/reneargento/algorithms-sedgewick-wayne/issues/19
// Based on https://github.com/ChangeMyUsername/algorithms-sedgewick-python/blob/master/chapter_3/module_3_2.py
public class Exercise21 {

    private class BinarySearchTree<Key extends Comparable<Key>, Value> extends chapter3.section2.BinarySearchTree<Key, Value> {

        public Key randomKey() {
            if (isEmpty()) {
                return null;
            }

            int randomIndex = StdRandom.uniform(size());
            return select(randomIndex);
        }
    }

    public static void main(String[] args) {
        Exercise21 exercise21 = new Exercise21();
        BinarySearchTree<Integer, String> binarySearchTree = exercise21.new BinarySearchTree<>();

        //Test put()
        binarySearchTree.put(5, "Value 5");
        binarySearchTree.put(1, "Value 1");
        binarySearchTree.put(9, "Value 9");
        binarySearchTree.put(2, "Value 2");
        binarySearchTree.put(0, "Value 0");
        binarySearchTree.put(99, "Value 99");

        //Test size()
        StdOut.println("Size: " + binarySearchTree.size() + " Expected: 6\n");

        //Test get() and keys()
        for (Integer key : binarySearchTree.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTree.get(key));
        }

        StdOut.println("\nRandom keys:");
        StdOut.println(binarySearchTree.randomKey());
        StdOut.println(binarySearchTree.randomKey());
        StdOut.println(binarySearchTree.randomKey());
        StdOut.println(binarySearchTree.randomKey());
        StdOut.println(binarySearchTree.randomKey());
        StdOut.println(binarySearchTree.randomKey());
    }

}


Question: Is delete() commutative? (Does deleting x, then y give the same result as deleting y, then x?)

Answer:
3.2.23

No, delete() is not commutative.

Counterexample:
Tree:
    A
  B   D
     C

1- Delete A, then B
    C
  B   D

    C
      D

2- Delete B, then A
    A
      D
     C

    D
   C

Thanks to faame (https://github.com/faame) for reporting an issue with the counterexample.
https://github.com/reneargento/algorithms-sedgewick-wayne/issues/230


Question: Memory usage. Compare the memory usage of BST with the memory usage of BinarySearchST and SequentialSearchST for N key-value pairs, under the assumptions described in Section 1.4 (see Exercise 3.1.21). Do not count the memory for the keys and values themselves, but do count references to them. Then draw a diagram that depicts the precise memory usage of a BST with String keys and Integer values (such as the ones built by FrequencyCounter), and then estimate the memory usage (in bytes) for the BST built when FrequencyCounter uses BST for Tale of Two Cities.

Answer:
3.2.27 - Memory usage

* BST
   object overhead -> 16 bytes
   Node reference (root) -> 8 bytes
   Node
      object overhead -> 16 bytes
      extra overhead for reference to the enclosing instance -> 8 bytes
      Key reference (key) -> 8 bytes
      Value reference (value) -> 8 bytes
      Node reference (left) -> 8 bytes
      Node reference (right) -> 8 bytes
      int value (size) -> 4 bytes
      padding -> 4 bytes
            (N Node references -> 64N bytes)
Amount of memory needed: 16 + 8 + (16 + 8 + 8 + 8 + 8 + 8 + 4 + 4)N = 64N + 24 bytes

* BinarySearchST
   object overhead -> 16 bytes
   Key[] reference (keys) -> 8 bytes
   Value[] reference (values) -> 8 bytes
   int value (size) -> 4 bytes
   padding -> 4 bytes
   Key[]
      object overhead -> 16 bytes
      int value (length) -> 4 bytes
      padding -> 4 bytes
      N Key references -> between 8N and 32N bytes (the resizing array may be 25% to 100% full)
   Value[]
      object overhead -> 16 bytes
      int value (length) -> 4 bytes
      padding -> 4 bytes
      N Value references -> between 8N and 32N bytes (the resizing array may be 25% to 100% full)
Amount of memory needed: 16 + 8 + 8 + 4 + 4 + 16 + 4 + 4 + (8N to 32N) + 16 + 4 + 4 + (8N to 32N) = (16N to 64N) + 88 bytes

* SequentialSearchST
   object overhead -> 16 bytes
   Node reference (first) -> 8 bytes
   Node
      object overhead -> 16 bytes
      extra overhead for reference to the enclosing instance -> 8 bytes
      Key reference (key) -> 8 bytes
      Value reference (value) -> 8 bytes
      Node reference (next) -> 8 bytes
            (N Node references -> 48N bytes)
   int value (size) -> 4 bytes
   padding -> 4 bytes
Amount of memory needed: 16 + 8 + (16 + 8 + 8 + 8 + 8)N + 4 + 4 = 48N + 32 bytes

String object (Java 7 and later) = 56 + 2C bytes, where C is the number of characters in its char[] array
Integer object = 24 bytes

Memory usage of a BST with String keys and Integer values: 64N + 24 bytes (BST) + (56 + 2C bytes + 24 bytes) * N
= 64N + 24 + (80 + 2C) * N bytes = 2CN + 144N + 24 bytes

Diagram

Assuming an average of 5 characters per String, the BST uses 2CN + 144N + 24 = 10N + 144N + 24 = 154N + 24 bytes

    Memory usage (bytes)
             15,400,024 |                                                         *
                154,024 |                              *
                 15,424 |      *
                        -------------------------------------------------------------
Number of keys/values   0     100                     1000                     100000

For FrequencyCounter, `N` in the BST formula must be the number of distinct keys, not the total number of word occurrences. If `D` is the number of distinct words and `C` is the average number of characters in those distinct words, the BST memory estimate including String keys and Integer values is:

`64D + 24 + (56 + 2C + 24)D = (144 + 2C)D + 24` bytes.

For example, if the distinct keys average 6 characters, the estimate is `156D + 24` bytes. Do not multiply by the total word count unless every word occurrence is a distinct key.


Question: Equal key check. Write a method hasNoDuplicates() that takes a Node as argument and returns true if there are no equal keys in the binary tree rooted at the argument node, false otherwise. Assume that the test of the previous exercise has passed.

Answer:
Since the previous order check has passed, an inorder traversal visits equal keys consecutively. Keep the previous key seen during that traversal and fail as soon as the same key appears twice.

```java
private Key previousKey;

public boolean hasNoDuplicates(Node x) {
    previousKey = null;
    return hasNoDuplicatesInOrder(x);
}

private boolean hasNoDuplicatesInOrder(Node x) {
    if (x == null) {
        return true;
    }

    if (!hasNoDuplicatesInOrder(x.left)) {
        return false;
    }

    if (previousKey != null && x.key.compareTo(previousKey) == 0) {
        return false;
    }
    previousKey = x.key;

    return hasNoDuplicatesInOrder(x.right);
}
```

The traversal is linear in the number of nodes and uses space proportional to the tree height, aside from the one saved key.


Question: Certification. Write a method isBST() that takes a Node as argument and returns true if the argument node is the root of a binary search tree, false otherwise. Hint: This task is also more difficult than it might seem, because the order in which you call the methods in the previous three exercises is important.

Answer:
The certification should compose the three preceding checks in the order required by their preconditions: first verify that subtree counts describe a binary tree, then verify ordering, then run the duplicate-key check that assumes the keys are already in inorder order.

```java
public boolean isBST(Node x) {
    if (x == null) {
        return true;
    }
    if (!isBinaryTree(x)) {
        return false;
    }
    if (!isOrdered(x, minKey(x), maxKey(x))) {
        return false;
    }
    return hasNoDuplicates(x);
}

private boolean isBinaryTree(Node x) {
    if (x == null) {
        return true;
    }

    int leftSize = x.left == null ? 0 : x.left.N;
    int rightSize = x.right == null ? 0 : x.right.N;

    if (x.N != leftSize + rightSize + 1) {
        return false;
    }
    return isBinaryTree(x.left) && isBinaryTree(x.right);
}

private boolean isOrdered(Node x, Key min, Key max) {
    if (x == null) {
        return true;
    }
    if (x.key.compareTo(min) < 0 || x.key.compareTo(max) > 0) {
        return false;
    }
    return isOrdered(x.left, min, x.key) && isOrdered(x.right, x.key, max);
}

private Key minKey(Node x) {
    Key min = x.key;
    if (x.left != null) {
        Key leftMin = minKey(x.left);
        if (leftMin.compareTo(min) < 0) {
            min = leftMin;
        }
    }
    if (x.right != null) {
        Key rightMin = minKey(x.right);
        if (rightMin.compareTo(min) < 0) {
            min = rightMin;
        }
    }
    return min;
}

private Key maxKey(Node x) {
    Key max = x.key;
    if (x.left != null) {
        Key leftMax = maxKey(x.left);
        if (leftMax.compareTo(max) > 0) {
            max = leftMax;
        }
    }
    if (x.right != null) {
        Key rightMax = maxKey(x.right);
        if (rightMax.compareTo(max) > 0) {
            max = rightMax;
        }
    }
    return max;
}
```

This catches bad subtree counts, keys outside the allowed range, and equal keys. The order matters because `hasNoDuplicates()` relies on the inorder sequence being sorted.