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.
Related
I am trying to implement quicksort in java and I am struggling to implement it in an efficient manner. I believe the problem is with my recursive calls but I cannot determine how to fix it. I am using compares to see how many times comparisons are made in hopes of determining where the problem is. The only thing I can think of is requiring a conditional around my recursive statements because the amount of compares needed is the same whether the inputted array is already sorted or seemingly randomized.
public int quickSort(int[] arr, int left, int right) {
//left is lowest index
//right is highest index
int compares = 0;
//calls insertion sort once subsets get smaller than 7 elements
if (right - left < 6) {
compares += insertSort(arr, left, right);
return compares;
}
//calculate random pivot
int pivotIndex = randomInt(left, right);
int pivotValue = arr[pivotIndex];
//moves pivot value to rightmost index
int temp;
temp = arr[pivotIndex];
arr[pivotIndex] = arr[right];
arr[right] = temp;
int pivotFinal = left;
//determines how many values are lower than the pivot, swapping
//smaller values with larger values along the way
for (int i = left; i < right; i++) {
compares++;
if (arr[i] <= pivotValue) {
temp = arr[i];
arr[i] = arr[pivotFinal];
arr[pivotFinal] = temp;
pivotFinal++;
}
}
//moves pivot to final position so that quicksort is complete
temp = arr[pivotFinal];
arr[pivotFinal] = arr[right];
arr[right] = temp;
compares += quickSort(arr, left, pivotIndex - 1);
compares += quickSort(arr, pivotIndex + 1, right);
return compares;
}
public void main() {
QuickSort qs = new QuickSort();
int n = 60;
int[] array = qs.GenerateRandomSequence(n);
int compares = qs.quickSort(array);
}
With an n of 60, one of the sequences required more than 4 million compares, which is much, much worse than the actual worst case runtime.
You have a couple of bugs with your indexes. Your recursion needs to be using your final pivot position.
compares += quickSort(arr, left, pivotFinal - 1);
compares += quickSort(arr, pivotFinal + 1, right);
And you're treating your "right" index differently in different spots. Probably easiest to just use "<=" in your loop
for (int i = left; i < right; i++) // assumes right is after the last index
arr[pivotIndex] = arr[right]; // assumes right IS the last index
So I think my partition method works but I cannot understand or figure out how to do the kthSmallest method. I no longer get out of bounds errors with my partition method which leads me to think that it works and with testing it seems to work. However, my kthSmallest method often gets stuck in an infinite loop and when it does return a value, it is never the correct value.
I have seen examples online that place the pivot between the two subarrays however for our assignment the pivot is always at the end so I often get confused looking at these examples.
Here is what I have:
class Median{
static int kthSmallest(int[] arr, int left, int right, int k){
int divider = partition(arr, left, right);
if(divider == k-1){
return arr[right];
}
else if(divider > k-1){
return kthSmallest(arr, left, divider-1, k);
}
else{
return kthSmallest(arr, divider, right, (k - divider-1));
}
}
static int partition(int[] arr, int left, int right){
int pivot = arr[right];
int index = left;
for(int i = left; i < right-1; i++){
if(arr[i] < pivot){
swap(arr, index, i);
index++;
}
}
printArr(arr);
System.out.println("divider: " + index);
return index;
}
static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void printArr(int[] arr){
System.out.println();
for(int index = 0; index < arr.length - 1; index++){
System.out.print(arr[index] + ", ");
}
System.out.print(arr[arr.length-1]);
System.out.println();
}
public static void main(String[] args){
int[] arr = {34,-1,0,5,3};
printArr(arr);
//System.out.println(partition(arr, 0, arr.length-1));
//printArr(arr);
System.out.println(kthSmallest(arr, 0, arr.length - 1, 2));
}
}
It seems like what you are trying to do is implement the QuickSelect algorithm, so I recommend taking a look at the Wikipedia article.
Looking at your code, I think you have misinterpreted the "pivot at the end" bit. I believe that what your requirements want is to select the pivot from the end, and then place it in between the two sublists so that your list looks right. For example,
34 -1 0 5 3
should become
-1 0 3 34 5
not
-1 0 34 5 3, where your pivot is not doing its job correctly.
There are also a few problems with your kthSmallest method. Double check the values you pass along in your recursion and you also are missing a case which might cause infinite recursion. Spoilers for if you are absolutely stuck:
You since you don't re-index the list, you should change '(k - divider -1)' to just k.
If left == right then you get unnecessary recursion, so you should just return in that case.
In your partition method, make sure you iterate far enough in the part of the list you are partitioning. Again, just in case you're really stumped:
for(int i = left; i < right-1; i++) should become for(int i = left; i <= right-1; i++)
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;
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;
}
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.