I am working on a coms assignment and have hit a huge wall. We are working with quicksort / partition.
/**
* An implementation of the SELECT algorithm of Figure 1 of the project
* specification. Returns the ith order statistic in the subarray
* arr[first], ..., arr[last]. The method must run in O(n) expected time,
* where n = (last - first + 1).
*
* #param arr
* - The data to search in
* #param first
* - The leftmost boundary of the subarray (inclusive)
* #param last
* - The rightmost boundary of the subarray (inclusive)
* #param i
* - The requested order statistic to find
* #return - The ith order statistic in the subarray
*
* #throws IllegalArgumentException
* - If i < 1 or i > n
*/
public static int select(int[] arr, int first, int last, int i) {
if(first == last){
return first;
}
int pivot = (arr.length / 2);
pivot = partition(arr, first, last, pivot-1);
if(i == pivot){
return arr[i];
} else if(i < pivot){
return select(arr, first, pivot-1, i);
} else {
return select(arr, pivot+1, last, i);
}
}
The above is a 'select' method, I believe it is an implementation of quicksort. When I run it with my partition method, I keep getting errors of ArrayIndex out of bounds, I am wondering if the way I chose my pivot is causing these errors...
The below is a partition and a swap method I have written. The partition method works from what I can tell, I made a int[] of 10 and ran it multiple times, using different pivot points. Each time it threw out the arr sorted the way it should be.
public static int partition(int[] arr, int first, int last, int pivot){
int pValue = arr[pivot];
swap(arr, last, pivot);
int temp = first;
for (int i = first; i < last; i++) {
if(arr[i] < pValue){
swap(arr, temp, i);
temp++;
}
}
swap(arr, last, temp);
return arr[pivot];
}
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
The rest of the assignment builds off of the select method, and I have been slamming me head into a wall for two days to get it to work correctly.... To the point actually where I am second guessing my choice in degree. I guess as a secondary question how many of you guys hit these walls and lost all confidence in yourselves? The last few assignments, with a bit of help, made sense, but now I am here and just lost in the dark...
PS. Sorry if I sound all sappy, it's been a rough weekend and the above has been a huge pain.
First off, the best way to debug this at this point is put print statements before every array access that prints what index is being looked at. This will tell you right away where the error is happening. The following is a guess at what might be happening. There may be other culprits.
What happens if the pivot is the first or last element (like which will always happen on an array of 2 elements)? Then when you select on pivot + 1 or pivot - 1, as you do at the end of the select function, you'll go off the end of the array. Or when you partition on pivot - 1. It seems you need to flesh out the base case your recursive function. This is the most likely culprit.
You're picking your pivot based on the length of the entire array rather than the length of the subarray, which is from first to last instead of from 0 to arr.length. If first and last have always been the the first and last element of the entire array, then I doubt this is the problem (though it still will be a problem when tested more thoroughly).
Related
Mr professor has assigned us the task of writing a custom qucksort algorithm that we must implement using his outline ( I can't write my own from scratch, I must use his). He calls it smartQuickSort, and what makes this algorithm "custom" is that we have to calculate the averages on each side of the pivot point which is then used to sort the array. The algorithm uses a class called SmartQuickSortPivot which has int values left and right to hold the averages on the left/right side respectively.
I've written numerous quick sort algorithms in several languages but I cannot, for the life of me, get this one to sort correctly. I've spent 3 days rewriting and debugging this thing with no success, so i'm really hoping someone could help me out as i'm about to pull all of my hair out. Starting from the "skeleton code" he gave us (which includes commented instructions), this is my latest attempt:
/**
* split4SmartQuickSort splits the array (from first to last) into two subarrays, left and right, using the
* provided splitVal. It needs to calculate on the fly the average of all the elements of the left subarray
* and average of all elements of the right subarray, and store the two averages in the #pivot object.
* The following implementation is only copy of the code from
* the split function (from line 247) and you should enhance the function to implement what we need to calculate the averages
* as the pivot for the left and right subarray.
*
* Please be noted that splitVal may not even exist in the array since we choose the average.
* But this should not impact the correctness algorithm of splitting and sorting.
* #param first
* #param last
* #param splitVal
* #param leftRightAverages
* #return
*/
static int split4SmartQuickSort(int first, int last, int splitVal, SmartQuickSortPivot leftRightAverages)
{
int saveF = first;
int leftAvg = 0;
int leftCount = 0;
int rightAvg = 0;
int rightCount = 0;
boolean onCorrectSide;
first++;
do
{
onCorrectSide = true;
while (onCorrectSide) // move first toward last
if (values[first] > splitVal)
onCorrectSide = false;
else
{
//I think my average calculations here are wrong,
//but nothing I have tried works correctly
leftAvg += first;
leftCount++;
first++;
leftRightAverages.left = leftAvg / leftCount;
onCorrectSide = (first <= last);
}
onCorrectSide = (first <= last);
while (onCorrectSide) // move last toward first
if (values[last] <= splitVal)
onCorrectSide = false;
else
{
//I think my average calculations here are wrong,
//but nothing I have tried works correctly
rightAvg += last;
rightCount++;
last--;
leftRightAverages.right = rightAvg / rightCount;
onCorrectSide = (first <= last);
}
if (first < last)
{
swap(first, last);
first++;
last--;
}
} while (first <= last);
swap(saveF, last);
//I think this is one of my problems. Not sure
//what I should be returning here
return last;
}
/**
* Smart quick sort allows the use of a better splitting value (the pivot value) when to split the array
* into two. In this algorithm, we will use the average of the array (subarray) of all elements as the pivot.
*
* Each call to split (split4SmartQuickSort method), the splitValue will be passed and also the split4SmartQuickSort
* will return the averages of left subarray and right subarray. The two averages, each will be used for the
* following calls to smartQuickSort.
*
* #param first the first element
* #param last the last element
* #param splitVal the pivot value for splitting the array
*/
static void smartQuickSort(int first, int last, int splitVal)
{
if (first < last)
{
int splitPoint;
SmartQuickSortPivot leftRightAverages = new SmartQuickSortPivot();
splitPoint = split4SmartQuickSort(first, last, splitVal, leftRightAverages);
if (first <= splitPoint)
{
smartQuickSort(first, splitPoint - 1, leftRightAverages.left);
}
if (last >= splitPoint)
{
smartQuickSort(splitPoint + 1, last, leftRightAverages.right);
}
}
}
Here is the class used to store the averages to the left/right of the pivot point:
public class SmartQuickSortPivot {
public int left;
public int right;
}
And finally the main method used for testing:
public static void main(String[] args)
{
//initValues();
printValues();
System.out.println("values is sorted: " + isSorted());
System.out.println();
//quickSort(0, values.length - 1);
/** you can either compute the average first as the first pivot or simplify choose the first one as the pivot */
smartQuickSort(0, values.length - 1, values[4]);
printValues();
System.out.println("values is sorted: " + isSorted());
System.out.println();
}
}
The line I commented out, //quickSort(0, values.length - 1); is the algorithm I wrote that does not include the leftRightAverages object argument but is essentially the same, and it works perfectly, so i'm very confused why I can't get the "custom" smartQuickSort to work. For simplicity, I commented out the initValues() method and instead used a preset array that looks like this:
static int[] values = {2,5,1,66,89,44,32,51,8,6}; // values to be sorted
Things I've tried (and failed at):
1.) Move the lines leftRightAverages.left = leftAvg / leftCount; , leftRightAverages.right = rightAvg / rightCount; outside of the do-while loop, which (I think) due to the recursive nature of the function, eventually gives me a divide by zero RTE.
2.) Change the return value of split4SmartQuickSort() from last to different combinations of rightLeftAverages.left and rightLeftAverages.right, which causes a stack overflow from the recursion. This is where I am really confused, as I'm not exactly sure what this method should be returning in this particular implementation of quick sort (and more importantly, how to properly calculate it).
I think my issue here is twofold; I'm either not correctly calculating the averages on each side of the pivot (I've used numerous pivot points and none of them seem to make a difference), and I'm not returning the proper calculation from the split4SmartQuickSort() method itself. If I remove the rightLeftAverages object from the method argument and use a more traditional approach to quick sort, the algorithm works fine. This is why I think those 2 issues I listed are why the algorithm doesn't function correctly. the return value from split4SmartQuickSort() (I think) acts as the new pivot point for sorting, using the splitVal argument as the original pivot point.
Yes this is my homework, but I've put hours of genuine effort into this thing, with no luck. My prof doesn't answer emails over the weekend and his office hours are during one of my other classes, so I have nowhere else to turn.
I think that you have problems with this because it's hard in this case to use one integer split point. Here is why:
Imagine that at some of the algorithm you got 44, 51, 89, 66 to partition with the average of 62.5 ~ 62. If you use 62 as pivot element there is uncertainty what to return as a split point (because you can return index 1 or 2 (values 51 or 89 correspondingly)).
Let's suppose that you pick 2. This will lead to invalid algorithm (let's remember that the split point (pivot) a_j is the point that divides array into two subarrays such for each i < j a_i < a_j and for each k > j a_j < a_k) because 89 !< 66 and cannot be a split point.
What you kind of need to do is to return something in the middle as a split point. To do this you need to return SmartQuickSortPivot object instead of int and use its left/right values as ending/starting indexes for your left/right arrays.
import java.util.Arrays;
public class Temp {
public static class SmartQuickSortPivot {
public int left;
public int right;
}
static int[] values = {2,5,1,66,89,44,32,51,8,6}; // values to be sorted
/**
* split4SmartQuickSort splits the array (from first to last) into two subarrays, left and right, using the
* provided splitVal. It needs to calculate on the fly the average of all the elements of the left subarray
* and average of all elements of the right subarray, and store the two averages in the #pivot object.
* The following implementation is only copy of the code from
* the split function (from line 247) and you should enhance the function to implement what we need to calculate the averages
* as the pivot for the left and right subarray.
*
* Please be noted that splitVal may not even exist in the array since we choose the average.
* But this should not impact the correctness algorithm of splitting and sorting.
* #param first
* #param last
* #param splitVal
* #param leftRightAverages
* #return
*/
static SmartQuickSortPivot split4SmartQuickSort(int first, int last, int splitVal, SmartQuickSortPivot leftRightAverages)
{
int i = first,j = last;
int sumLeft = 0;
int sumRight = 0;
while (i < j) {
while (values[i] < splitVal){
sumLeft += values[i];
i++;
}
while (values[j] > splitVal){
sumRight += values[j];
j--;
}
if (i < j) {
swap(i, j);
}
}
leftRightAverages.left = (i - first == 0) ? values[first] : sumLeft / (i - first);
leftRightAverages.right = (last - j == 0) ? values[last] : sumRight / (last - j);
SmartQuickSortPivot smartQuickSortPivot = new SmartQuickSortPivot();
smartQuickSortPivot.left = i;
smartQuickSortPivot.right = j;
return smartQuickSortPivot;
}
private static void swap(int i, int j) {
int temp = values[i];
values[i] = values[j];
values[j] = temp;
}
/**
* Smart quick sort allows the use of a better splitting value (the pivot value) when to split the array
* into two. In this algorithm, we will use the average of the array (subarray) of all elements as the pivot.
*
* Each call to split (split4SmartQuickSort method), the splitValue will be passed and also the split4SmartQuickSort
* will return the averages of left subarray and right subarray. The two averages, each will be used for the
* following calls to smartQuickSort.
*
* #param first the first element
* #param last the last element
* #param splitVal the pivot value for splitting the array
*/
static void smartQuickSort(int first, int last, int splitVal)
{
if (first < last)
{
SmartQuickSortPivot splitPoint;
SmartQuickSortPivot leftRightAverages = new SmartQuickSortPivot();
splitPoint = split4SmartQuickSort(first, last, splitVal, leftRightAverages);
if (first < splitPoint.left)
{
smartQuickSort(first, splitPoint.left - 1, leftRightAverages.left);
}
if (last > splitPoint.right)
{
smartQuickSort(splitPoint.right + 1, last, leftRightAverages.right);
}
}
}
public static void main(String[] args)
{
/** you can either compute the average first as the first pivot or simplify choose the first one as the pivot */
smartQuickSort(0, values.length - 1, values[5]);
System.out.println(Arrays.toString(values));
}
}
Thanks to the great advice below, I got the algorithm working, but it still was not sorting duplicates correctly (infinite loop when dupes encountered). After playing with the code, I now have a complete working algorithm. The change was in the split4SmartQuickSort() only, so here is that method updated:
static SmartQuickSortPivot split4SmartQuickSort
(int first, int last, int splitVal, SmartQuickSortPivot leftRightAverages)
{
int f = first;
int l = last;
int sumLeft = 0;
int sumRight = 0;
while (f < l)
{
while (values[f] < splitVal)
{
sumLeft += values[f];
f++;
}
while (values[l] > splitVal)
{
sumRight += values[l];
l--;
}
if (f <= l)
{
swap(f, l);
//handling duplicates in the list
if (values[f] == values[l])
{
f++;
}
}
}
if (f - first == 0)
{
leftRightAverages.left = values[first];
}
else
{
leftRightAverages.left = sumLeft / (f - first);
}
if (last - l == 0)
{
leftRightAverages.right = values[last];
}
else
{
leftRightAverages.right = sumRight / (last - l);
}
//create SmartQuickSortPivot object to be returned. Used in
//smartQuickSort as the split point for sorting
SmartQuickSortPivot sqsp = new SmartQuickSortPivot();
sqsp.left = f;
sqsp.right = l;
return sqsp;
}
And finally, the smartQuickSort() algorithm:
static void smartQuickSort(int first, int last, int splitVal)
{
if (first < last)
{
SmartQuickSortPivot splitPoint;
SmartQuickSortPivot leftRightAverages = new SmartQuickSortPivot();
splitPoint = split4SmartQuickSort(first, last, splitVal, leftRightAverages);
if (first <= splitPoint.left)
{
smartQuickSort(first, splitPoint.left - 1, leftRightAverages.left);
}
if (last >= splitPoint.right)
{
smartQuickSort(splitPoint.right + 1, last, leftRightAverages.right);
}
}
}
Thanks again to #shyyko-serhiy, as they deserve most of the credit for getting this thing working :)
I'm trying to use these quick sort methods to figure out how many comparison are happening. We are given a global variable that does the counting but we aren't able to use the global variable when we hand it in. Instead we need to recursively count the comparisons. Now I am trying to figure out how to do that and I'm not looking for the answer, I'm trying to get on the right steps on how to solve this problem. I've been trying things for a couple hours now and no luck.
static int qSortCompares = 0; // GLOBAL var declaration
/**
* The swap method swaps the contents of two elements in an int array.
*
* #param The array containing the two elements.
* #param a The subscript of the first element.
* #param b The subscript of the second element.
*/
private static void swap(int[] array, int a, int b) {
int temp;
temp = array[a];
array[a] = array[b];
array[b] = temp;
}
public static void quickSort(int array[]) {
qSortCompares = 0;
int qSCount = 0;
doQuickSort(array, 0, array.length - 1);
}
/**
* The doQuickSort method uses the QuickSort algorithm to sort an int array.
*
* #param array The array to sort.
* #param start The starting subscript of the list to sort
* #param end The ending subscript of the list to sort
*/
private static int doQuickSort(int array[], int start, int end) {
int pivotPoint;
int qSTotal = 0;
if (start < end) {
// Get the pivot point.
pivotPoint = partition(array, start, end);
// Note - only one +/=
// Sort the first sub list.
doQuickSort(array, start, pivotPoint - 1);
// Sort the second sub list.
doQuickSort(array, pivotPoint + 1, end);
}
return qSTotal;
}
/**
* The partition method selects a pivot value in an array and arranges the
* array into two sub lists. All the values less than the pivot will be
* stored in the left sub list and all the values greater than or equal to
* the pivot will be stored in the right sub list.
*
* #param array The array to partition.
* #param start The starting subscript of the area to partition.
* #param end The ending subscript of the area to partition.
* #return The subscript of the pivot value.
*/
private static int partition(int array[], int start, int end) {
int pivotValue; // To hold the pivot value
int endOfLeftList; // Last element in the left sub list.
int mid; // To hold the mid-point subscript
int qSCount = 0;
// see http://www.cs.cmu.edu/~fp/courses/15122-s11/lectures/08-qsort.pdf
// for discussion of middle point - This improves the almost sorted cases
// of using quicksort
// Find the subscript of the middle element.
// This will be our pivot value.
mid = (start + end) / 2;
// Swap the middle element with the first element.
// This moves the pivot value to the start of
// the list.
swap(array, start, mid);
// Save the pivot value for comparisons.
pivotValue = array[start];
// For now, the end of the left sub list is
// the first element.
endOfLeftList = start;
// Scan the entire list and move any values that
// are less than the pivot value to the left
// sub list.
for (int scan = start + 1; scan <= end; scan++) {
qSortCompares++;
qSCount++;
if (array[scan] < pivotValue) {
endOfLeftList++;
// System.out.println("Pivot=" + pivotValue + "=" + endOfLeftList + ":" + scan);
swap(array, endOfLeftList, scan);
}
}
// Move the pivot value to end of the
// left sub list.
swap(array, start, endOfLeftList);
// Return the subscript of the pivot value.
return endOfLeftList;
}
/**
* Print an array to the Console
*
* #param A
*/
public static void printArray(int[] A) {
for (int i = 0; i < A.length; i++) {
System.out.printf("%5d ", A[i]);
}
System.out.println();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
final int SIZE = 10;
int[] A = new int[SIZE];
// Create random array with elements in the range of 0 to SIZE - 1;
System.out.printf("Lab#2 Sorting Algorithm Performance Analysis\n\n");
for (int i = 0; i < SIZE; i++) {
A[i] = (int) (Math.random() * SIZE);
}
System.out.printf("Unsorted Data = %s\n", Arrays.toString(A));
int[] B;
// Measure comparisons and time each of the 4 sorts
B = Arrays.copyOf(A, A.length); // Need to do this before each sort
long startTime = System.nanoTime();
quickSort(B);
long timeRequired = (System.nanoTime() - startTime) / 1000;
System.out.printf("Sorted Data = %s\n", Arrays.toString(B));
System.out.printf("Number of compares for quicksort = %8d time = %8d us Ratio = %6.1f compares/us\n", qSortCompares, timeRequired, qSortCompares / (double) timeRequired);
// Add code for the other sorts here ...
}
The instructions give some hints but I am still lost:
The quicksort method currently counts the # of comparisons by using a global variable. This is not a good programming technique. Modify the quicksort method to count comparisons by passing a parameter. This is a little trickier as the comparisons are done in the partition method. You should be able to see that the number of comparisons can be determined before the call to the partition method. You will need to return this value from the Quicksort method and modify the quickSort header to pass this value into each recursive call. You will need to add the counts recursively.
As an alternative to the recursive counting, you can leave the code as is and complete the lab without the modification.
The way I have been looking at this assignment I made a variable in the partition method called qSCount which when it is called will count how many comparisons were made. However I can't use that variable because I am not returning it. And I'm not sure how I would use recursion in that state. My idea was after each time qSCount had a value I could somehow store it in doQuickSort method under qSTotal. But then again the hint is saying I need to make a parameter in quicksort so I am all sorts of confused.
In order to count something with a recursive method (without a global variable) we need to return it. You have:
private static int doQuickSort(int array[], int start, int end)
This is the right idea. But since the comparisons actually happen within
private static int partition(int array[], int start, int end)
you need to have partition return how many comparisons were made.
This leaves us with two options:
We can either create or use an existing Pair class to have this method return a pair of integers instead of just one (the pivot).
We can create a counter class and pass a counter object around and have the counting done there. This eliminates the need to return another value since the parameter could be used to increase the count.
I have written a recursive method for a partition sort that sorts the array however when I use an array of more than 10-20 elements the program takes a really long time to complete (On my computer a bubble sort of a 100,000 int array will take about 15-20 seconds but with an array of only 30 ints my partition sort is taking around 45 seconds to be sorted.
Here is the code.
public static int[] partitionSortRecursive(int[] array, int beginning, int end)
{
if (end < beginning)
return array;
int pivot = (array[beginning] + array[end]) / 2;
int firstUnknown = beginning;
int lastS1 = beginning - 1;
int firstS3 = end + 1;
while (firstUnknown < firstS3)
{
if (array[firstUnknown] == pivot)
{
firstUnknown++;
}
else if (array[firstUnknown] > pivot)
{
firstS3--;
int temp = array[firstUnknown];
array[firstUnknown] = array[firstS3];
array[firstS3] = temp;
}
else
{
lastS1++;
int temp = array[firstUnknown];
array[firstUnknown] = array[lastS1];
array[lastS1] = temp;
firstUnknown++;
}
}
partitionSortRecursive(array, 0, lastS1);
partitionSortRecursive(array, firstS3, end);
return array;
}
You do not use the correct pivot element. You calculate the average of the left and right value but you have to take a sample value from the sub array to partition instead.
You may take the rightmost, the center or any other element. So your first line of codes should look like this
int pivot = array[(beginning + end) / 2];
// or
int pivot = array[end];
You could also take any other element (e.g. random)
EDIT: This does not solve the performance issue.
To my understanding, quick sort will divide an array into two sub arrays A and B where all elements in A are smaller than any element in B and then perform the same operation onto the two sub arrays.
So the basic call structure should be like this
void DoSort (array, i, j)
{
pivot = Partition (array, i, j)
DoSort (array, i,pivot)
DoSort (array, pivot + 1, j)
}
Put your implementation is basically
void DoSort (array, i, j)
{
pivot = Partition (array, i, j)
DoSort (array, 0, pivot) // <<<<<< notice the '0' instead of 'i'
DoSort (array, pivot + 1, j)
}
So you always start from the very beginning of the original array which will most likely take a while
Instead of direct recoursive call like this
partitionSortRecursive(array, 0, lastS1);
partitionSortRecursive(array, firstS3, end);
Organize internal stack where you can save index pairs. While the stack is not empty get the next pair from the stack. In the end of function don't call the same function but put in the stack 2 pairs (0, lastS1) and (firstS3, end)
I have found this code in github for quickselect algorithm otherwise known as order-statistics. This code works fine.
I do not understand medianOf3 method, which is supposed to arrange the first, middle and last index in sorted order. but actually it does not when I ouput the array, after calling the medianof3 method.
I could follow this method as to what it is doing except the last call of swap(list, centerIndex, rightIndex - 1);. can someone explain why this is called?
import java.util.Arrays;
/**
* This program determines the kth order statistic (the kth smallest number in a
* list) in O(n) time in the average case and O(n^2) time in the worst case. It
* achieves this through the Quickselect algorithm.
*
* #author John Kurlak <john#kurlak.com>
* #date 1/17/2013
*/
public class Quickselect {
/**
* Runs the program with an example list.
*
* #param args The command-line arguments.
*/
public static void main(String[] args) {
int[] list = { 3, 5, 9, 10, 7, 40, 23, 45, 21, 2 };
int k = 6;
int median = medianOf3(list, 0, list.length-1);
System.out.println(median);
System.out.println("list is "+ Arrays.toString(list));
Integer kthSmallest = quickselect(list, k);
if (kthSmallest != null) {
System.out.println("The kth smallest element in the list where k=" + k + " is " + kthSmallest + ".");
} else {
System.out.println("There is no kth smallest element in the list where k=" + k + ".");
}
System.out.println(Arrays.toString(list));
}
/**
* Determines the kth order statistic for the given list.
*
* #param list The list.
* #param k The k value to use.
* #return The kth order statistic for the list.
*/
public static Integer quickselect(int[] list, int k) {
return quickselect(list, 0, list.length - 1, k);
}
/**
* Recursively determines the kth order statistic for the given list.
*
* #param list The list.
* #param leftIndex The left index of the current sublist.
* #param rightIndex The right index of the current sublist.
* #param k The k value to use.
* #return The kth order statistic for the list.
*/
public static Integer quickselect(int[] list, int leftIndex, int rightIndex, int k) {
// Edge case
if (k < 1 || k > list.length) {
return null;
}
// Base case
if (leftIndex == rightIndex) {
return list[leftIndex];
}
// Partition the sublist into two halves
int pivotIndex = randomPartition(list, leftIndex, rightIndex);
int sizeLeft = pivotIndex - leftIndex + 1;
// Perform comparisons and recurse in binary search / quicksort fashion
if (sizeLeft == k) {
return list[pivotIndex];
} else if (sizeLeft > k) {
return quickselect(list, leftIndex, pivotIndex - 1, k);
} else {
return quickselect(list, pivotIndex + 1, rightIndex, k - sizeLeft);
}
}
/**
* Randomly partitions a set about a pivot such that the values to the left
* of the pivot are less than or equal to the pivot and the values to the
* right of the pivot are greater than the pivot.
*
* #param list The list.
* #param leftIndex The left index of the current sublist.
* #param rightIndex The right index of the current sublist.
* #return The index of the pivot.
*/
public static int randomPartition(int[] list, int leftIndex, int rightIndex) {
int pivotIndex = medianOf3(list, leftIndex, rightIndex);
int pivotValue = list[pivotIndex];
int storeIndex = leftIndex;
swap(list, pivotIndex, rightIndex);
for (int i = leftIndex; i < rightIndex; i++) {
if (list[i] <= pivotValue) {
swap(list, storeIndex, i);
storeIndex++;
}
}
swap(list, rightIndex, storeIndex);
return storeIndex;
}
/**
* Computes the median of the first value, middle value, and last value
* of a list. Also rearranges the first, middle, and last values of the
* list to be in sorted order.
*
* #param list The list.
* #param leftIndex The left index of the current sublist.
* #param rightIndex The right index of the current sublist.
* #return The index of the median value.
*/
public static int medianOf3(int[] list, int leftIndex, int rightIndex) {
int centerIndex = (leftIndex + rightIndex) / 2;
if (list[leftIndex] > list[rightIndex]) {
swap(list, leftIndex, centerIndex);
}
if (list[leftIndex] > list[rightIndex]) {
swap(list, leftIndex, rightIndex);
}
if (list[centerIndex] > list[rightIndex]) {
swap(list, centerIndex, rightIndex);
}
swap(list, centerIndex, rightIndex - 1);
return rightIndex - 1;
}
/**
* Swaps two elements in a list.
*
* #param list The list.
* #param index1 The index of the first element to swap.
* #param index2 The index of the second element to swap.
*/
public static void swap(int[] list, int index1, int index2) {
int temp = list[index1];
list[index1] = list[index2];
list[index2] = temp;
}
}
So I wrote the original code, but I did a poor job of making it readable.
Looking back at it, I don't think that line of code is necessary, but I think it is a small optimization. If we remove the line of code and return centerIndex, it seems to work without any issues.
Unfortunately, the optimization it performs should be refactored out of medianOf3() and moved into randomPartition().
Essentially, the optimization is that we want to "partially sort" our subarray as much as possible before partitioning it. The reason being this: the more sorted our data is, the better our future partition choices will be, which means our run time will hopefully be closer to O(n) than O(n^2). In the randomPartition() method, we move the pivot value to the far right of the subarray we're looking at. This moves the far right value into the middle of the subarray. This is not desired since the far right value is supposed to be a "larger value". My code tries to prevent this by placing the pivot index right next to the rightmost index. Then, when the pivot index is swapped with the rightmost index in randomPartition(), the "larger" rightmost value doesn't move into the middle of the subarray, but stays near the right.
Function medianOf3 is to define order of left median and right. Last statement
swap(list, centerIndex, rightIndex - 1)
is used to achieve following precondition of a sort:
However,
instead of recursing into both sides, as in quicksort, quickselect
only recurses into one side – the side with the element it is
searching for. This reduces the average complexity from O(n log n) (in
quicksort) to O(n) (in quickselect).
And then algorithm continues with:
for (int i = leftIndex; i < rightIndex; i++) {
if (list[i] <= pivotValue) {
swap(list, storeIndex, i);
storeIndex++;
}
}
in order to
that the values to the left of the pivot are less than or equal to
the pivot and the values to the right of the pivot are greater than
the pivot.
I am doing an algorithms class project in which we must modify an implementation of QuickSort with suggested improvements. One of these suggestions is as follows: Do not single out the pivot in the array and avoid the last swap of the partition method.
I'm having trouble understanding exactly what he means by this. Without a pivot, how is it even still QuickSort anymore? Any insight into what this could imply would be appreciated. This is the Java code to be modified.
public void quickSort() {
recQuickSort(0, nElems - 1);
}
public void recQuickSort(int left, int right) {
if (left >= right)
return;
long pivot = a[right];
int mid = partition(left, right, pivot);
recQuickSort(left, mid - 1);
recQuickSort(mid + 1, right);
} // end recQuickSort()
public void swap(int dex1, int dex2) { // swap two elements
long temp = a[dex1]; // A into temp
a[dex1] = a[dex2]; // B into A
a[dex2] = temp; // temp into B
} // end swap()
public int partition(int left, int right, long pivot) {
// assuming pivot == a[right]
int leftPtr = left - 1; // left of the first element
int rightPtr = right; // position of pivot
while (true) {
while (a[++leftPtr] < pivot)
; // find bigger
while (leftPtr < rightPtr && a[--rightPtr] >= pivot)
; // find smaller
if (leftPtr >= rightPtr) // if pointers cross,
break; // partition done
else
// not crossed, so
swap(leftPtr, rightPtr); // swap elements
} // end while(true)
swap(leftPtr, right); // restore pivot
return leftPtr; // return pivot location
} // end partition()
I'm not going to try to implement it for you, but my interpretation of this suggested "improvement" is that he wants you to still choose a pivot value and partition the array into sections according to which side of that value they're on, but not treat the array entry containing that value specially.
It shouldn't be hard to do, but it won't improve performance of the algorithm at all, and I doubt that it's much of an improvement in any other sense.
It looks like he doesn't want you to pivot on an actual element in the array. The point of the last swap in the partition() method is that he moves the pivot into the correct spot in the array (note that he never moves a pivot element after a partition() call).
Edit If you are confused about "not using a pivot", you are still using a pivot... it's just not a pivot in the array. Imagine doing the quicksort by hand, you can pick any arbitrary value to pivot with.
The issue is that that swap doesn't really negatively impact performance at all. On the other hand, this should be a quick change...