diff --git a/QuickSort/src/lab/QuickSort.java b/QuickSort/src/lab/QuickSort.java index f48ea8c..2c032a0 100755 --- a/QuickSort/src/lab/QuickSort.java +++ b/QuickSort/src/lab/QuickSort.java @@ -5,59 +5,114 @@ import frame.SortArray; /** * Abstract superclass for the Quicksort algorithm. * + * Note: I got into a small optimization competition with a fellow student, trying + * to get read and write ops to an absolute minimum. While this code does not include + * most of the write optimizations for the sake of legibility, there is a fair bit of + * passing local variables between functions to save a few read ops. + * I do believe this to be well within the rules of the assignment as values are not + * cached in an array-like structure or passed between recursion levels. + * All optimizations merely cut down on duplicate / unnecessary reads and writes. + * * @author NAJI + * @author Nils Rollshausen */ public abstract class QuickSort { // DO NOT modify this method public abstract void Quicksort(SortArray records, int left, int right); - // You may add additional methods here + /** + * Partitions an interval of the SortArray so that elements of the left part are <= pivot + * and elements on the right are >= pivot. This variant does not use any pre-cached values for array bounds + * @param pivot The value of the pivot element + * @param records The SortArray to partition + * @param leftBound Left bound index of the interval to partition + * @param rightBound Right bound index of the interval to partition + * @return index of the partition + */ protected int partition(SortingItem pivot, SortArray records, int leftBound, int rightBound) { if(leftBound == rightBound) return leftBound; + // Lookup bound values SortingItem leftValue = records.getElementAt(leftBound); SortingItem rightValue = records.getElementAt(rightBound); + // Use actual implementation with pre-cached bound values return partition(pivot, records, leftBound, rightBound, leftValue, rightValue); } + /** + * Partitions an interval of the SortArray so that elements of the left part are <= pivot + * and elements on the right are >= pivot. As the value of the leftmost array element is + * equal to the pivot value when using said element as the pivot, we can avoid reading that + * value from the array _again_ by passing it as an additional parameter + * @param pivot The value of the pivot element + * @param records The SortArray to partition + * @param leftBound Left bound index of the interval to partition + * @param rightBound Right bound index of the interval to partition + * @param leftValue Value of the element at index leftBound + * @return index of the partition + */ protected int partition(SortingItem pivot, SortArray records, int leftBound, int rightBound, SortingItem leftValue) { if(leftBound == rightBound) return leftBound; + // Look up the right bound value SortingItem rightValue = records.getElementAt(rightBound); + // Use actual implementation with pre-cached bounds return partition(pivot, records, leftBound, rightBound, leftValue, rightValue); } + /** + * Partitions an interval of the SortArray so that elements of the left part are <= pivot + * and elements on the right are >= pivot. As the value of the bounding array elements is + * already known in some usecases, we can avoid reading these values from the array again + * by passing them as additional parameters + * @param pivot The value of the pivot element + * @param records The SortArray to partition + * @param leftBound Left bound index of the interval to partition + * @param rightBound Right bound index of the interval to partition + * @param leftValue Value of the element at index leftBound + * @param rightValue Value of the element at index rightBound + * @return index of the partition + */ protected int partition(SortingItem pivot, SortArray records, int leftBound, int rightBound, SortingItem leftValue, SortingItem rightValue) { - if(leftBound == rightBound) - return leftBound; - int leftIndex = leftBound; int rightIndex = rightBound; SortingItem temp; + /* + * Using the Hoare partitioning scheme here as opposed to the one presented in the lecture + * for approximately three times less swap operations compared to the Lomuto scheme + * See https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme for details + * Essentially, increment / decrement left / right indices until a pair of values that are + * the wrong part of the list is found, then swap that pair and continue + */ while(true){ + // As long as the items at left index are smaller than the pivot, they are on the right side while(leftIndex < rightBound && leftValue.compareTo(pivot) < 0) { leftIndex += 1; leftValue = records.getElementAt(leftIndex); } + // Same for items at rightIndex larger than the pivot while(rightIndex > leftBound && rightValue.compareTo(pivot) > 0) { rightIndex -= 1; rightValue = records.getElementAt(rightIndex); } + // As soon as both indices cross, no more swaps have to be made and the partition is complete if(leftIndex >= rightIndex) { return rightIndex; } + // If the indices are not equal (or crossed over), swap the two items that are in the wrong spot records.setElementAt(leftIndex, rightValue); records.setElementAt(rightIndex, leftValue); + // Also swap the local copies temp = rightValue; rightValue = leftValue; leftValue = temp; diff --git a/QuickSort/src/lab/QuickSortA.java b/QuickSort/src/lab/QuickSortA.java index 9b13e20..a85405d 100755 --- a/QuickSort/src/lab/QuickSortA.java +++ b/QuickSort/src/lab/QuickSortA.java @@ -8,24 +8,23 @@ public class QuickSortA extends QuickSort { * Quicksort algorithm implementation to sort a SorrtArray by choosing the * pivot as the first (leftmost) element in the list * - * @param records - * - list of elements to be sorted as a SortArray - * @param left - * - the index of the left bound for the algorithm - * @param right - * - the index of the right bound for the algorithm + * @param records List of elements to be sorted as a SortArray + * @param left The index of the left bound for the algorithm + * @param right The index of the right bound for the algorithm * @return Returns the sorted list as SortArray */ @Override public void Quicksort(SortArray records, int left, int right) { - // implement the Quicksort A algorithm to sort the records - // (choose the pivot as the first (leftmost) element in the list) - + // Lists of length 1 or smaller are trivially sorted if(right - left < 1) return; + // Use the leftmost element as the pivot SortingItem pivot = records.getElementAt(left); + // Pass the value of the pivot as the leftValue to partition to save an array read int p = partition(pivot, records, left, right, pivot); + + // Sort the two sub-lists recursively (p being the position of the partition) Quicksort(records, left, p); Quicksort(records, p+1, right); } diff --git a/QuickSort/src/lab/QuickSortB.java b/QuickSort/src/lab/QuickSortB.java index 9a23067..015c0c7 100755 --- a/QuickSort/src/lab/QuickSortB.java +++ b/QuickSort/src/lab/QuickSortB.java @@ -8,52 +8,64 @@ public class QuickSortB extends QuickSort { * Quicksort algorithm implementation to sort a SorrtArray by choosing the * pivot as the median of the elements at positions (left,middle,right) * - * @param records - * - list of elements to be sorted as a SortArray - * @param left - * - the index of the left bound for the algorithm - * @param right - * - the index of the right bound for the algorithm + * @param records List of elements to be sorted as a SortArray + * @param left The index of the left bound for the algorithm + * @param right The index of the right bound for the algorithm * @return Returns the sorted list as SortArray */ @Override public void Quicksort(SortArray records, int left, int right) { - // implement the Quicksort B algorithm to sort the records - // (choose the pivot as the median value of the elements at position - // (left (first),middle,right(last))) + // Lists of length 1 or smaller are trivially sorted if(right - left < 1) return; + // Get the left and right bounding values (for median calculation) SortingItem leftValue = records.getElementAt(left); SortingItem rightValue = records.getElementAt(right); + // Calculate the median pivot of left, right and middle SortingItem pivot = getPivot(left, right, records, leftValue, rightValue); + + // Pass the already looked-up left and right values to partition to save two reads int p = partition(pivot, records, left, right, leftValue, rightValue); + + // Sort the two sub-lists recursively (p being the position of the partition) Quicksort(records, left, p); Quicksort(records, p+1, right); } + /** + * Method to get the median pivot out of the leftmost, rightmost and middle element of an array + * @param left Leftmost index + * @param right Rightmost index + * @param records The Array to work on + * @param l The value of the array at the left index + * @param r The value of the array at the right index + * @return The value of the chosen median pivot + */ private SortingItem getPivot(int left, int right, SortArray records, SortingItem l, SortingItem r) { + // Calculate the index of the middle element (rounded down) int mIndex = (int) Math.floor(left + (right - left) / 2); + // If the array has only two items, the left item is the middle item so we don't have to look it up again SortingItem m = (mIndex == left) ? l : records.getElementAt(mIndex); + SortingItem t = null; - // 'Sort' the three elements by doing two swaps if necessary, then return the middle (= median) one - - if(l.compareTo(m) > 0) { + // The median element is the middle element of the sorted three element list of left, middle and right + // 'Sort' the three elements by doing two swaps if necessary, then return the middle one + if(l.compareTo(m) > 0) { // (if l is larger than m, l and m need to be swapped) t = l; l = m; m = t; } - - if(r.compareTo(m) < 0) { + if(r.compareTo(m) < 0) { // (if r is smaller than the new m, r and m need to be swapped) t = r; r = m; m = t; } - return m; + return m; // return the new m } } diff --git a/QuickSort/src/lab/SortingItem.java b/QuickSort/src/lab/SortingItem.java index 83a68ec..34b1a01 100755 --- a/QuickSort/src/lab/SortingItem.java +++ b/QuickSort/src/lab/SortingItem.java @@ -4,6 +4,8 @@ package lab; * * This class represents one entry of the list that has to be sorted. * + * Added Comparable interface to be able to, well, compare items + * */ public class SortingItem implements Comparable{ @@ -24,8 +26,11 @@ public class SortingItem implements Comparable{ this.Status = otherItem.Status; } + // Compares self to another SortingItem, returns 0 if equal value, -1 if self is smaller, 1 if self is larger @Override public int compareTo(SortingItem arg0) { + // If the BookSerialNumbers are equal, we return the result of the ReaderID comparison, otherwise comparing BookSerialNumbers is sufficient + // (string comparisons are lexicographical) return this.BookSerialNumber.compareTo(arg0.BookSerialNumber) == 0 ? this.ReaderID.compareTo(arg0.ReaderID): this.BookSerialNumber.compareTo(arg0.BookSerialNumber); }