'Issues with Iterative quicksort
I am having issues with 2 pivot strategies, median of 3 random elements (corresponding to the quickSortThreeRandomPivot() method) & random element (corresponding to the quickSortRandomPivot() method). I am pretty sure the error is originating from where I set the range of random numbers (indices).
Currently, this is code I am using to try to accomplish this: quickSortRandomPivot():
pivot = (int) list.get(ThreadLocalRandom.current().nextInt((last - first) + first));
quickSortThreeRandomPivot():
int num1 = (int) list.get(ThreadLocalRandom.current().nextInt(**(last - first) + first)**);
int num2 = (int) list.get(ThreadLocalRandom.current().nextInt(**(last - first) + first)**);
int num3 = (int) list.get(ThreadLocalRandom.current().nextInt(**(last - first) + first)**);
pivot = medianOfThreeNums(num1, num2, num3);
In my implementation, I am trying to sort the list "in place". So, to update the range of indices to generate random number(s) from, I used the equation (last - first) + first. If the current subarray is being sorted is the left side of the list, the range of indices should be 0 to pivotIndex - 1 (first == 0, last == pivotIndex - 1). If the current subarray is being sorted is the right side of the list, the range of indices should be pivotIndex + 1 to the size of the list - 1 (first == pivotIndex + 1, last == list.size() - 1).
However, when I run my code, I get the corresponding error below for array sizes greater than or equal to 100:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 100 out of bounds for length 100
at QuickSorter.quickSortRandomPivot(QuickSorter.java:261)
at QuickSorter.timedQuickSort(QuickSorter.java:31)
at DriverClass.main(DriverClass.java:56)
Is there something wrong with my logic? Any advice will help a lot, as my professor seems to be unable to help me resolve the issue :/
Here is my code:
Class that contains quicksorting methods:
import java.lang.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.io.*;
public class QuickSorter{
// This method takes in the randomly generated ArrayList and the specified pivot strategy, and sorts the
// ArrayList based on the specified pivot strategy.
// This method returns (roughly) the amount of time it took to sort the ArrayList.
public static <E extends Comparable<E>> Duration timedQuickSort(ArrayList<E> list, PivotStrategy pivotStrategy){
// Throw a NullPointerException if either of the arguments to this method are null.
if(list == null || pivotStrategy == null){
throw new NullPointerException("Error: the arguments to timedQuickSort() cannot be null.");
}
int first = 0; // Holds the index of the 1st element (0).
int last = list.size() - 1; // Holds the index of the last element.
Duration pivotStratDuration; // Used to determine the execution time of the selected pivot strategy.
// Determine the pivot strategy and call the appropriate function to begin sorting.
switch(pivotStrategy){
case FIRST_ELEMENT:
// Begin sorting and return the execution time of the quicksort operations.
pivotStratDuration = quickSortFirstElementPivot(list, first, last);
break;
case RANDOM_ELEMENT:
// Begin sorting and return the execution time of the quicksort operations.
pivotStratDuration = quickSortRandomPivot(list, first, last);
break;
case MEDIAN_OF_THREE_RANDOM_ELEMENTS:
// Begin sorting and return the execution time of the quicksort operations.
pivotStratDuration = quickSortThreeRandomPivot(list, first, last);
break;
case MEDIAN_OF_THREE_ELEMENTS:
// Begin sorting and return the execution time of the quicksort operations.
pivotStratDuration = quickSortThreePivot(list, first, last);
break;
default:
// Have the default case be the 1st element of the array as the pivot.
// Begin sorting and return the execution time of the quicksort operations.
pivotStratDuration = quickSortFirstElementPivot(list, first, last);
break;
}
// Return the execution time of the quicksort method, with the selected pivot strategy.
return pivotStratDuration;
}
/*
// This method sets the selected pivot (based on the PivotStrategy), places the pivot element at is correct position in the unsorted ArrayList,
// and places all smaller elements (smaller than the pivot) to the left of the pivot and all greater elements to the right of the pivot.
private static <E> int partition(ArrayList<E> currentList, int low, int high, int selectedPivot){
// Index of the smaller element indicates the right position of pivot found so far.
int i = (low - 1);
for(int j = low; j <= high - 1; j++){
// If current element is smaller than the pivot.
if((int) currentList.get(j) <= selectedPivot){
// Increment index of the smaller element.
i++;
swap(currentList, i, j);
}
}
swap(currentList, i + 1, currentList.indexOf(selectedPivot));
return (i + 1);
}
*/
private static <E> int partition(ArrayList<E> currentList, int low, int high, int selectedPivot){
// This loop terminates when low >= right.
while(true) {
while((int) currentList.get(low) < selectedPivot) {
low++;
}
while((int) currentList.get(high) > selectedPivot) {
high--;
}
if(low >= high){
return high;
}
swap(currentList, low, high);
}
}
/*
private static <E> int partition(ArrayList<E> currentList, int low, int high, int selectedPivot){
int leftPtr = low;
int rightPtr = high - 1;
while(true){
while((int) currentList.get(++leftPtr) < selectedPivot);
while(rightPtr > 0 && (int) currentList.get(--rightPtr) > selectedPivot);
if(leftPtr >= rightPtr){
break;
}
else{
swap(currentList, leftPtr, rightPtr);
}
}
swap(currentList, leftPtr, high);
return leftPtr;
}
*/
// This enum helps timedQuickSort determine what pivot strategy it should use to sort the ArrayList.
public static enum PivotStrategy{
// Pivot is the 1st element in the ArrayList.
FIRST_ELEMENT,
// Pivot is a randomly chosen element in the ArrayList (from index 0 to size - 1).
RANDOM_ELEMENT,
// Pivot is the median of 3 randomly chosen elements in the ArrayList (from index 0 to size - 1).
MEDIAN_OF_THREE_RANDOM_ELEMENTS,
// Pivot is the median of the 1st, middle, and last elements in the ArrayList.
MEDIAN_OF_THREE_ELEMENTS
};
// This helper method is used to swap 2 elements.
private static <E> void swap(ArrayList<E> currentList, int k, int r){
E temp = currentList.get(k);
currentList.set(k, currentList.get(r));
currentList.set(r, temp);
}
// This method generates a random ArrayList of integers of a specified size.
public static ArrayList<Integer> generateRandomList(int size){
// The size of the ArrayList should not be negative (or 0). If it is, throw an IllegalArgumentException.
if(size <= 0){
throw new IllegalArgumentException("Error: the size of the list cannot be less than or equal to 0.");
}
ArrayList<Integer> unsortedList = new ArrayList<>(); // Create an ArrayList to hold random & unsorted values.
// Generate a list of n random numbers.
for(int i = 0; i < size; i++){
Random randomNum = new Random();
unsortedList.add(i, randomNum.nextInt());
}
// Return the unsorted list of random numbers.
return unsortedList;
}
// This helper method finds the median value of 3 numbers (int).
private static <E extends Comparable<E>> E medianOfThreeNums(E a, E b, E c){
// Check if b is the median value (a.compareTo(b) < 0 is the same as a < b).
if((a.compareTo(b) < 0 && b.compareTo(c) < 0) || (c.compareTo(b) < 0 && b.compareTo(a) < 0)){
return b;
}
// Check if a is the median value.
else if(((b.compareTo(a) < 0 && a.compareTo(c) < 0) || (c.compareTo(a) < 0 && a.compareTo(b) < 0))){
return a;
}
else{ // Otherwise, c is the median value.
return c;
}
}
// This method performs the quick sort operations in an iterative fashion, with the 1st element in the array as the pivot.
// It returns the execution time of the quicksort operations.
private static <E> Duration quickSortFirstElementPivot(ArrayList<E> list, int first, int last){
int pivot; // Holds the pivot.
// Create an auxiliary stack.
int[] stack = new int[last - first + 1];
// Initialize the top of the stack.
int top = -1;
// Push the initial values of low and high onto the stack.
stack[++top] = first;
stack[++top] = last;
// The start and end variables are used to calculate the amount of time (in nano seconds) it takes to sort the list with the given pivot strategy.
long start1 = System.nanoTime();
// Keep popping off the stack while it is not empty.
while(top >= 0){
// Pop low and high.
last = stack[top--];
first = stack[top--];
// Set the pivot to the 1st element in the array.
pivot = (int) list.get(first);
// partitionIndex = partitioning index; array[partitionIndex] is now in its true location.
int partitionIndex = partition(list, first, last, pivot);
// If there are elements on the left side of the pivot, then push the left side of the array to the stack.
if(partitionIndex - 1 > first){
stack[++top] = first;
stack[++top] = partitionIndex - 1;
}
// If there are elements on the right side of the pivot, then push them to the right side of the array to the stack.
if(partitionIndex + 1 < last){
stack[++top] = partitionIndex + 1;
stack[++top] = last;
}
}
long end1 = System.nanoTime();
Duration pivotStratDuration = Duration.ofNanos(end1 - start1);
return pivotStratDuration;
}
// This method performs the quick sort operations in an iterative fashion, with a random element in the array as the pivot.
// It returns the execution time of the quicksort operations.
private static <E> Duration quickSortRandomPivot(ArrayList<E> list, int first, int last){
int pivot; // Holds the pivot.
// Create an auxiliary stack.
int[] stack = new int[last - first + 1];
// Initialize the top of the stack.
int top = -1;
// Push the initial values of low and high onto the stack.
stack[++top] = first;
stack[++top] = last;
// The start and end variables are used to calculate the amount of time (in nano seconds) it takes to sort the list with the given pivot strategy.
long start1 = System.nanoTime();
// Keep popping off the stack while it is not empty.
while(top >= 0){
//if (last - first < 2) { continue; }
// Pop low and high.
last = stack[top--];
first = stack[top--];
// Set the pivot as a random element contained within the array.
// Passing (last - first) + first reduces the range of the indices in the list after each while loop iteration.
// If the current sub array is being sorted is the left side of the original list, range of indices: (first == 0, last == pivotIndex - 1).
// If the current sub array is being sorted is the right side of the original list, range of indices: (first == pivotIndex + 1, last == list.size() - 1).
pivot = (int) list.get(ThreadLocalRandom.current().nextInt((last - first) + first));
// partitionIndex = partitioning index; array[partitionIndex] is now in its true location.
int partitionIndex = partition(list, first, last, pivot);
// If there are elements on the left side of the pivot, then push the left side of the array to the stack.
if(partitionIndex - 1 > first){
stack[++top] = first;
stack[++top] = partitionIndex - 1;
}
// If there are elements on the right side of the pivot, then push them to the right side of the array to the stack.
if(partitionIndex + 1 < last){
stack[++top] = partitionIndex + 1;
stack[++top] = last;
}
}
long end1 = System.nanoTime();
Duration pivotStratDuration = Duration.ofNanos(end1 - start1);
return pivotStratDuration;
}
// This method performs the quick sort operations in an iterative fashion, with the median of 3 random elements as the pivot.
// It returns the execution time of the quicksort operations.
private static <E> Duration quickSortThreeRandomPivot(ArrayList<E> list, int first, int last){
int pivot; // Holds the pivot.
// Create an auxiliary stack.
int[] stack = new int[last - first + 1];
// Initialize the top of the stack.
int top = -1;
// Push the initial values of low and high onto the stack.
stack[++top] = first;
stack[++top] = last;
// The start and end variables are used to calculate the amount of time (in nano seconds) it takes to sort the list with the given pivot strategy.
long start1 = System.nanoTime();
// Keep popping off the stack while it is not empty.
while(top >= 0){
//if (last - first < 2) { continue; }
// Pop low and high.
last = stack[top--];
first = stack[top--];
// Set the pivot as 3 random elements contained within the array.
// Passing (last - first) + first reduces the range of the indices in the list after each while loop iteration.
// If the current sub array is being sorted is the left side of the original list, range of indices: (first == 0, last == pivotIndex - 1).
// If the current sub array is being sorted is the right side of the original list, range of indices: (first == pivotIndex + 1, last == list.size() - 1).
int num1 = (int) list.get(ThreadLocalRandom.current().nextInt((last - first) + first));
int num2 = (int) list.get(ThreadLocalRandom.current().nextInt((last - first) + first));
int num3 = (int) list.get(ThreadLocalRandom.current().nextInt((last - first) + first));
pivot = medianOfThreeNums(num1, num2, num3);
// partitionIndex = partitioning index; array[partitionIndex] is now in its true location.
int partitionIndex = partition(list, first, last, pivot);
// If there are elements on the left side of the pivot, then push the left side of the array to the stack.
if(partitionIndex - 1 > first){
stack[++top] = first;
stack[++top] = partitionIndex - 1;
}
// If there are elements on the right side of the pivot, then push them to the right side of the array to the stack.
if(partitionIndex + 1 < last){
stack[++top] = partitionIndex + 1;
stack[++top] = last;
}
}
long end1 = System.nanoTime();
Duration pivotStratDuration = Duration.ofNanos(end1 - start1);
return pivotStratDuration;
}
// This method performs the quick sort operations in an iterative fashion, with the median of the 1st, middle, and last elements in the array as the pivot.
// It returns the execution time of the quicksort operations.
private static <E> Duration quickSortThreePivot(ArrayList<E> list, int first, int last){
int pivot; // Holds the pivot.
// Create an auxiliary stack.
int[] stack = new int[last - first + 1];
// Initialize the top of the stack.
int top = -1;
// Push the initial values of low and high onto the stack.
stack[++top] = first;
stack[++top] = last;
// The start and end variables are used to calculate the amount of time (in nano seconds) it takes to sort the list with the given pivot strategy.
long start1 = System.nanoTime();
// Keep popping off the stack while it is not empty.
while(top >= 0){
// Pop low and high.
last = stack[top--];
first = stack[top--];
// Calculate (roughly) the middle index of ArrayList.
int mid = (first + last) / 2;
// Set the pivot as the median of the 1st, middle (roughly), and last elements within the array.
int number1 = (int) list.get(first);
int number2 = (int) list.get(mid);
int number3 = (int) list.get(last);
pivot = medianOfThreeNums(number1, number2, number3);
// partitionIndex = partitioning index; array[partitionIndex] is now in its true location.
int partitionIndex = partition(list, first, last, pivot);
// If there are elements on the left side of the pivot, then push the left side of the array to the stack.
if(partitionIndex - 1 > first){
stack[++top] = first;
stack[++top] = partitionIndex - 1;
}
// If there are elements on the right side of the pivot, then push them to the right side of the array to the stack.
if(partitionIndex + 1 < last){
stack[++top] = partitionIndex + 1;
stack[++top] = last;
}
}
long end1 = System.nanoTime();
Duration pivotStratDuration = Duration.ofNanos(end1 - start1);
return pivotStratDuration;
}
// This method prints out the list to a specified output file.
public static <E> void printArray(ArrayList<E> list, PrintWriter outputFile){
int newlineCounter = 0; // Used to display a newline after a certain number of elements.
for(int i = 0; i < list.size(); i++){
outputFile.print(list.get(i) + " ");
// Display a newline after 20 elements save been written to the output file to increase readability.
if(newlineCounter == 20){
outputFile.println();
newlineCounter = 0; // Reset counter.
}
else{
newlineCounter++;
}
}
}
}
Driver Class (where main method is located):
import java.io.*;
import java.util.*;
import java.time.*;
/*
How to run program from cmd (windows):
1st command: cd FilePath
2nd command: javac DriverClass.java
3rd command: java DriverClass arraySize reportsFileName unsortedArrayFileName sortedArrayFileName
Note: For the 1st command, the 2 files (DriverClass.java and QuickSorter.java) must be in the same folder.
*/
// args[0] = array size, args[1] = filename for reports file, args[2] = filename to store unsorted array.
// args[3] = filename to store sorted array.
public class DriverClass {
public static void main(String [] args){
PrintWriter reportsFile; // Object for writing the execution time of quicksort for each pivot strategy to an output file.
PrintWriter unsortedListFile; // Object for writing the contents of the unsorted array to an output file.
PrintWriter sortedListFile; // Object for writing the contents of the sorted array to an output file.
// Convert args[0] (string; represents the array size) to an int.
int arraySize = Integer.valueOf(args[0]);
try{
reportsFile = new PrintWriter(new File(args[1])); // Creates an instance of PrintWriter and creates an output file for writing the execution time to.
unsortedListFile = new PrintWriter(new File(args[2])); // Creates an instance of PrintWriter and creates an output file for writing the unsorted list to.
sortedListFile = new PrintWriter(new File(args[3])); // Creates an instance of PrintWriter and creates an output file for writing the sorted list to.
try{
// Generate an array of random numbers with the specified size.
ArrayList<Integer> list = QuickSorter.generateRandomList(arraySize);
// Write the contents of the unsorted list to the unsortedListFile.
QuickSorter.printArray(list, unsortedListFile);
// Write the array size to the reports file.
reportsFile.println("Array Size = " + arraySize + "\n");
// Create 4 pointers that reference the same ArrayList (list).
// This allows each pivot strategy to sort the same set of numbers.
ArrayList<Integer> tempList1 = new ArrayList<>(list);
ArrayList<Integer> tempList2 = new ArrayList<>(list);
ArrayList<Integer> tempList3 = new ArrayList<>(list);
ArrayList<Integer> tempList4 = new ArrayList<>(list);
Duration pivotStrategyDuration; // Holds the execution time of a pivot strategy.
// Begin sorting the array, using the 1st element pivot strategy.
pivotStrategyDuration = QuickSorter.timedQuickSort(tempList1, QuickSorter.PivotStrategy.FIRST_ELEMENT);
// Write the execution time to the reports output file.
reportsFile.println("FIRST_ELEMENT: " + pivotStrategyDuration + "\n");
// Begin sorting the array, using the a random element pivot strategy.
pivotStrategyDuration = QuickSorter.timedQuickSort(tempList2, QuickSorter.PivotStrategy.RANDOM_ELEMENT);
// Write the execution time to the reports output file.
reportsFile.println("RANDOM_ELEMENT: " + pivotStrategyDuration + "\n");
// Begin sorting the array, using the median of 3 random elements pivot strategy.
pivotStrategyDuration = QuickSorter.timedQuickSort(tempList3, QuickSorter.PivotStrategy.MEDIAN_OF_THREE_RANDOM_ELEMENTS);
// Write the execution time to the reports output file.
reportsFile.println("MEDIAN_OF_THREE_RANDOM _ELEMENTS: " + pivotStrategyDuration + "\n");
// Begin sorting the array, using the median of the 1st, middle, and last element pivot strategy.
pivotStrategyDuration = QuickSorter.timedQuickSort(tempList4, QuickSorter.PivotStrategy.MEDIAN_OF_THREE_ELEMENTS);
// Write the execution time to the reports output file.
reportsFile.println("MEDIAN_OF_THREE_ELEMENTS: " + pivotStrategyDuration + "\n");
// Write the one of the sorted lists to the sortedListFile.
QuickSorter.printArray(tempList1, sortedListFile);
}
catch(IllegalArgumentException x){ // Catches the exception where the arraySize <= 0.
reportsFile.println(x.getMessage()); // Write the error message to the report's file.
System.exit(1); // Terminate the program (1 = failure).
}
catch(NullPointerException x){ // Catches the exception where one of the arguments passed to timedQuickSort is null.
reportsFile.println(x.getMessage()); // Write the error message to the report's file.
System.exit(1); // Terminate the program (1 = failure).
}
// Close all the output files.
reportsFile.close();
unsortedListFile.close();
sortedListFile.close();
}
catch(FileNotFoundException x){ // Catches the exception where the one of the output files are unable to be created.
System.out.println("Error: unable to create an output file.");
System.exit(1); // Terminate the program (1 = failure).
}
System.exit(0); // Terminate the program (0 = success).
}
}
Instructions on how to run program:
If you are using IDE that is Eclipse, IntelliJ, or NetBeans, set the program arguments as "arraySize" "reports.txt" "unsortedArray.txt" "sortedArray.txt"
If you not using one of the 3 IDEs above, you must use cmd (windows) with the following commands: 1st command: cd FilePath 2nd command: javac DriverClass.java 3rd command: java DriverClass arraySize reportsFileName unsortedArrayFileName sortedArrayFileName
Note: For the 1st command, the 2 files (DriverClass.java and QuickSorter.java) must be in the same folder.
The report.txt file just holds the size of the array and the execution time for the 4 different pivot strategies.
Thank you for your help and expertise!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
