Quicksort sorts all but a few items - java

I have spent a lot of time attempting to figure out what I don't have right; debugging and all but I just cannot seem to put my finger on why my quicksort misses a few items of sorting.
I've copied my code for sort and partition below. I have a feeling it's something extremely obvious I am over looking but I have spent endless hours debugging, researching, and rewriting my code and it always turns out the same.
// quicksort the subarray from a[lo] to a[hi]
private void sort(Comparable[] a, int lo, int hi) {
if((hi-lo)>1){
int pivot = partition(a,lo,hi);
sort(a,lo,pivot);
sort(a,pivot+1,hi);
}
}
// partition the subarray a[lo .. hi] by returning an index j
// so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
private int partition(Comparable[] a, int lo, int hi) {
//find middle
int pivotIndex = (hi+lo)/2;
//pick pivot
Comparable pivot = a[pivotIndex];
//create left and right pointers
int left = lo, right = hi;
//start comparing
//compare until left passes right
while(left<right){
//while left is less than pivot move on
while(a[left].compareTo(pivot) < 0) left++;
//while right is greater than pivot move on
while(a[right].compareTo(pivot) > 0) right--;
//if the pointers have passed each other we're done
//if a[left] is greater than a[right] swap them
if(a[left].compareTo(a[right]) > 0){
Comparable holder = a[left];
a[left] = a[right];
a[right] = holder;
//increment/decrement
left++; right--;
}
}
return right;
}

Just remove the left++; right--; and everything should be fine.

Related

How to count number of comparisons in a quicksort algorithm?

I am trying to count the number of comparisons made with a quicksort algorithm. To my understanding, it should take about 1000 comparisons to sort an array with 80 elements. Right now with what I have, it is spitting out 80 elements takes about 250 comparisons. This doesn't seem right. I'm assuming my counts are in the wrong place, but I really don't know. I know that some of the code isn't here, but it would be way to long if I added everything. Any help would be appreciated.
Here is my code:
public void quickSort(int[] data) {
quickSort(data, 0, data.length - 1);
}
private void quickSort(int[] data, int min, int max) {
if (min < max) {
// create partitions
int indexofpartition = partition(data, min, max);
// sort the left partition (lower values)
quickSort(data, min, indexofpartition - 1);
// sort the right partition (higher values)
quickSort(data, indexofpartition + 1, max);
}
}
private int partition(int[] data, int min, int max) {
int partitionelement;
int left, right;
int middle = (min + max) / 2;
// use the middle data value as the partition element
partitionelement = data[middle];
// move it out of the way for now
swap(data, middle, min);
left = min;
right = max;
while (left < right) {
// search for an element that is > the partition element
while (left < right && data[left] <= partitionelement)
left++;
count++;
// search for an element that is < the partition element
while (data[right] > partitionelement)
right--;
count++;
// swap the elements
if (left < right)
swap(data, left, right);
}
// move the partition element into place
swap(data, min, right);
return right;
}
Need to add braces to your inner while loop. Currently only left++ and right-- is getting executed inside the loop. Updated code would be like:
// search for an element that is > the partition element
while (left < right && data[left] <= partitionelement) {
left++;
count++;
}
// search for an element that is < the partition element
while (data[right] > partitionelement && right >= min) {
right--;
count++;
}
Additional hint: Add a check for the index in the second while loop. Else, it can throw an exception if there isn't any value lesser than the partitionelement.

Understanding why Java selection rank returns max() as final result

I am working out a solution to the following question:
Describe an algorithm to find the smallest one million numbers in one
billion numbers. Assume that the computer memory can hold all one
billion numbers.
The book gives the a selection rank solution but I am having a hard time understanding a few parts of it:
public static int partition(int[] array, int left, int right, int pivot) {
while (true) {
while (left <= right && array[left] <= pivot) {
left++;
}
while (left <= right && array[right] > pivot) {
right--;
}
if (left > right) {
return left - 1;
}
swap(array, left, right);
}
}
public static int rank(int[] array, int left, int right, int rank) {
int pivot = array[randomIntInRange(left, right)];
int leftEnd = partition(array, left, right, pivot); // returns end of left partition
int leftSize = leftEnd - left + 1;
if (leftSize == rank + 1) {
return max(array, left, leftEnd);
} else if (rank < leftSize) {
return rank(array, left, leftEnd, rank);
} else {
return rank(array, leftEnd + 1, right, rank - leftSize);
}
}
I understand most of it, but I do no understand the following two lines above:
if (leftSize == rank + 1) {
return max(array, left, leftEnd);
1. Why are we returning the max of the three variables?
2. Shouldn't we just be returning array[left:leftEnd] or something of that nature?
Congratulations on trying to learn something by carefully studying a book. It's a key skill that seems to be getting rarer.
It makes general sense if the definition of the return value of rank is "there exist exactly a million numbers less than or equal to rank." The definition of max would be something like:
int t = array[left];
for (int i = left + 1; i <= leftEnd; i++)
t = Math.max(t, array[i]);
return t;
Returning the max value is beyond the problem statement and kind of weird. It would be better and simpler just to partition the elements so that the max million are at the top: array[0] through array[999999]. Then find the max only if that's actually needed
Note that because rank is tail recursive, there's a simple iterative version of the same code that I think would be clearer.
I'm also not convinced this code is correct. leftSize == rank in the check makes more sense than leftSize == rank + 1. But without more definitions and calling code, it's hard to say for sure.
The same rank function is used in Cracking the Coding Interview 6th Edition p. 569 (aside from the line: if (leftSize == rank + 1) { is modified to rank - 1).
The max function is provided, listed below:
/* Get largest element in array between left and right indices */
int max(int[] array, int left, int right)
{
int max = Integer.MIN_VALUE;
for(int i = left; i <= right; i++)
{
max = Math.max(array[i], max);
}
return max;
}
As for an explanation: rank(array, rank) returns the element which would be at the ith position of the sorted array.
leftEnd is the position of the pivot. If there are rank - 1 elements before the pivot element, then there are rank elements including said pivot.
I believe that the pivot will always be the max element, therefore the call to max(array, left, leftEnd); can be replaced with return pivot;

Inplace Quicksort in Java

For refreshing some Java I tried to implement a quicksort (inplace) algorithm that can sort integer arrays. Following is the code I've got so far. You can call it by sort(a,0,a.length-1).
This code obviously fails (gets into an infinite loop) if both 'pointers' i,j point each to an array entry that have the same values as the pivot. The pivot element v is always the right most of the current partition (the one with the greatest index).
But I just cannot figure out how to avoid that, does anyone see a solution?
static void sort(int a[], int left, int right) {
if (right > left){
int i=left, j=right-1, tmp;
int v = a[right]; //pivot
int counter = 0;
do {
while(a[i]<v)i++;
while(j>0 && a[j]>v)j--;
if( i < j){
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
} while(i < j);
tmp = a[right];
a[right] = a[i];
a[i] = tmp;
sort(a,left,i-1);
sort(a,i+1,right);
}
}
When preforming a Quicksort I strongly suggest making a separate method for partitioning to make the code easier to follow (I'll show an example below). On top of this a good way of avoiding worst case run time is shuffling the array you're sorting prior to preforming the quick sort. Also I used the first index as the partitioning item instead of the last.
For example:
public static void sort (int[] a)
{
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
}
private static void sort(int[] a, int lo, int hi)
{
if (hi <= lo) return;
int j = partition(a, lo, hi) // the addition of a partitioning method
sort(a, lo, j-1);
sort(a, j+1, hi);
}
private static int partition(int[] a, int lo, int hi)
{
int i = lo, j = hi + 1, tmp = 0;
int v = a[lo];
while (true)
{
while (a[i++] < v) if (i == hi) break;
while (v < a[j--]) if (j == lo) break;
if (i >= j) break;
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
tmp = a[lo];
a[lo] = a[j];
a[j] = temp;
return j;
}
On top of this if you want a really good example on how Quicksort works (as a refresher) see here.
This should work (will check for correctness in a bit, it works!):
EDIT: I previously made a mistake in error checking. I forgot to add 2 more conditions, here is the amended code.
public static void main (String[] args) throws java.lang.Exception
{
int b[] = {10, 9, 8, 7, 7, 7, 7, 3, 2, 1};
sort(b,0,b.length-1);
System.out.println(Arrays.toString(b));
}
static void sort(int a[], int left, int right) {
if (right > left){
int i=left, j=right, tmp;
//we want j to be right, not right-1 since that leaves out a number during recursion
int v = a[right]; //pivot
do {
while(a[i]<v)
i++;
while(a[j]>v)
//no need to check for 0, the right condition for recursion is the 2 if statements below.
j--;
if( i <= j){ //your code was i<j
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
i++;
j--;
//we need to +/- both i,j, else it will stick at 0 or be same number
}
} while(i <= j); //your code was i<j, hence infinite loop on 0 case
//you had a swap here, I don't think it's needed.
//this is the 2 conditions we need to avoid infinite loops
// check if left < j, if it isn't, it's already sorted. Done
if(left < j) sort(a,left,j);
//check if i is less than right, if it isn't it's already sorted. Done
// here i is now the 'middle index', the slice for divide and conquer.
if(i < right) sort(a,i,right);
}
}
This Code in the IDEOne online compiler
Basically we make sure that we also swap the value if the value of i/j is the same as the pivot, and break out of the recursion.
Also there was a check in the pseudocode for the length, as if we have an array of just 1 item it's already sorted (we forgot the base case), I thought we needed that but since you pass in the indexes and the entire array, not the subarray, we just increment i and j so the algorithm won't stick at 0 (they're done sorting) but still keep sorting an array of 1. :)
Also, we had to add 2 conditions to check if the array is already sorted for the recursive calls. without it, we'll end up sorting an already sorted array forever, hence another infinite loop. see how I added checks for if left less than j and if i less than right. Also, at that point of passing in i and j, i is effectively the middle index we split for divide and conquer, and j would be the value right before the middle value.
The pseudocode for it is taken from RosettaCode:
function quicksort(array)
if length(array) > 1
pivot := select any element of array
left := first index of array
right := last index of array
while left ≤ right
while array[left] < pivot
left := left + 1
while array[right] > pivot
right := right - 1
if left ≤ right
swap array[left] with array[right]
left := left + 1
right := right - 1
quicksort(array from first index to right)
quicksort(array from left to last index)
Reference: This SO question
Also read this for a quick refresher, it's implemented differently with an oridnary while loop
This was fun :)
Heres some simple code I wrote that doesn't initialize to many pointers and gets the job done in a simple manner.
public int[] quickSort(int[] x ){
quickSortWorker(x,0,x.length-1);
return x;
}
private int[] quickSortWorker(int[] x, int lb, int ub){
if (lb>=ub) return x;
int pivotIndex = lb;
for (int i = lb+1 ; i<=ub; i++){
if (x[i]<=x[pivotIndex]){
swap(x,pivotIndex,i);
swap(x,i,pivotIndex+1);
pivotIndex++;
}
}
quickSortWorker(x,lb,pivotIndex-1);
quickSortWorker(x,pivotIndex+1,ub);
return x;
}
private void swap(int[] x,int a, int b){
int tmp = x[a];
x[a]=x[b];
x[b]=tmp;
}

Quicksort not completely sorting provided array

My Quicksort seems to stop before completely sorting the array, and I've stared myself blind on the code.
I wrote the algorithm according to the related chapters in Java Software Structures - Designing and Using Data Structures (3rd Edition)
Quick Sort:
private static <T extends Comparable<T>> void quickSort(T[] array,int min, int max){
int pIndex;
if (max-min > 0) {
pIndex = partition(array, min, max);
quickSort(array, min, pIndex-1);
quickSort(array, pIndex+1, max);
}
}
Partition:
private static <T extends Comparable<T>> int partition(T[] array, int min, int max) {
int left, right;
T pivot, temp;
int middle = (min+max)/2;
left = min;
right = max;
pivot = array[middle];
while (left < right) {
while (array[left].compareTo(pivot) <= 0 && left < right)
left++;
while (array[right].compareTo(pivot) > 0)
right--;
if (left<right) {
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
temp = array[min];
array[min] = array[right];
array[right] = temp;
return right;
}
The input:
An int[10] array containing the values 0 through 9, shuffled.
The quicksort-function is thus called like: quicksort(nums, 0, nums.length-1)
The output (example):
0
2
1
3
7
4
5
6
8
9
As you can see, the end product seems to be somewhat on the way to a good end-product, but it's stopping prematurely somewhere.
Update:
None of the answers provided so far (the deleted ones included) worked. If nobody is able to spot the bug, would anyone kindly redirect me to a good source for generic algorithms in Java?
I even shamefully attempted to do a pure copypaste of the Quicksort algorithm from the book mentioned above, and while it compiled and ran, it resulted in the same, "almost-correct" output as above. I then questioned whether or not it may be my input data, but nope. It is simply an Integer-array of integers, no duplicates. It's a valid candidate to my understanding.
I was able to get quick sort to sort some test arrays with the following partition function.
private static <T extends Comparable<T>> int partition(T[] array, int min, int max) {
int left, right;
T pivot, temp;
int middle = (min+max)/2;
left = min;
right = max ;
pivot = array[middle];
while (left < right) {
while (array[left].compareTo(pivot) < 0 && left < right)
left++;
while (array[right].compareTo(pivot) > 0)
right--;
if (left<right) {
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
return right;
}
All I changed was the first compareTo comparison to be less than instead of less than or equal to. This allows the pivot to move in the array. This however does mean that the array CANNOT contain duplicates. I also removed the last swap as I couldn't tell what it was doing.
The problems stem from how you deal with the pivot. It doesn't actually partition the array properly.
This also works and allows duplicates.
private static <T extends Comparable<T>> int partition(T[] array, int min, int max) {
int left, right;
T pivot, temp;
int middle = (min+max)/2;
left = min + 1;
right = max ;
pivot = array[middle];
// move partition element to min index
temp = array[min];
array[min] = array[middle];
array[middle] = temp;
while (left < right) {
while (array[left].compareTo(pivot) <= 0 && left < right)
left++;
while (array[right].compareTo(pivot) > 0)
right--;
if (left<right) {
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
// move partition element to partition index
temp = array[min];
array[min] = array[right];
array[right] = temp;
return right;
}
I looked up a copy of the book. The comment tells you what the last swap was trying to do. Which makes my fix of adding a swap at the begging to move the partition element to the min index the correct fix.
while (array[left].compareTo(pivot) <= 0 && left < right)
left++;
while (array[right].compareTo(pivot) > 0)
right--;
These loops are usually written:
while (array[left++].compareTo(pivot) <= 0 && left < right)
;
while (array[right--].compareTo(pivot) > 0)
;
The idea is to stop at the first element that doesn't belong in this partition.

Quick Sort with random pivot in Java

I've been assigned to implement a quick sort with a random pivot point (because it's supposedly the most efficient/safest way), yet I've been slaving over a bogosort. Can anyone direct me on how to do it? And can someone help me look at my bogosort to see if I can save it anyway?
public static void Quick(int[] target, int lo, int hi) {
if(hi-lo==0){return;}
Random numberGenerator = new Random();
int pivot = (numberGenerator.nextInt(hi-lo)+lo);
int high;
for(high=hi; high>pivot ; high--){
if(target[high]<target[pivot]){ //if highest was smaller than pivot, move far end
if(high-pivot==1){
int temp=target[high];
target[high]=target[pivot];
target[pivot]=temp;
}
else{
int temp1 = target[pivot];
int temp2 = target[pivot+1];
target[pivot]=target[high];
target[pivot+1]=temp1;
target[high]=temp2;
}
}
}
int low;
for(low=lo; low<pivot ; low++){
if(target[low]>target[pivot]){ //if highest was smaller than pivot, move far end
if(pivot-low==1){
int temp=target[low];
target[low]=target[pivot];
target[pivot]=temp;
}
else{
int temp1 = target[pivot];
int temp2 = target[pivot-1];
target[pivot]=target[low];
target[pivot-1]=temp1;
target[low]=temp2;
}
}
}
if(low-lo>0){
Quick(target, lo, low-1);
}
if(hi-high>0){
Quick(target, high+1, hi);
}
}
See this pseudocode for inplace patitioning (from Wikipedia):
function partition(array, left, right, pivotIndex)
pivotValue := array[pivotIndex]
swap array[pivotIndex] and array[right] // Move pivot to end
storeIndex := left
for i from left to right - 1 // left ≤ i < right
if array[i] ≤ pivotValue
swap array[i] and array[storeIndex]
storeIndex := storeIndex + 1
swap array[storeIndex] and array[right] // Move pivot to its final place
return storeIndex
Notice it loops through the whole array (except the last index). The pivot isn't swapped until the end. In your code the pivot value keeps changing through the loop which doesn't seem correct.
Each time there is a swap the swap target (storeIndex above) should change.
Also you only need to swap values lower than the pivot to the left. If all the low values are to the left, then the high values will end up on the right.

Categories

Resources