As I was testing my quicksort I noticed another problem. Sometimes it arranges the array in alphabetical order and sometimes it does not. For example if I have p, o, j, l as my array it sorts it to j, o, l, p which is wrong because l should be before o However if I add a to the array it sorts to a, j, l, o, p which is correct. Why is this happening?
Code:
private ArrayList<String> sort(ArrayList<String> ar, int lo, int hi){
if (lo < hi){
int splitPoint = partition(ar, lo, hi);
sort(ar, lo, splitPoint);
sort(ar, splitPoint +1, hi);
}
return ar;
}
private int partition(ArrayList<String> ar, int lo, int hi){
String pivot = ar.get(lo);
lo--;
hi++;
while (true){
lo++;
hi--;
while (lo<hi && ar.get(lo).compareTo(pivot) < 0){
lo++;
}
while (hi>lo && ar.get(hi).compareTo(pivot) >= 0){
hi--;
}
if (lo<hi){
swap(ar, lo, hi);
}else {
return hi;
}
}
}
private ArrayList<String> swap(ArrayList<String> ar, int a, int b){
String temp = ar.get(a);
ar.set(a, ar.get(b));
ar.set(b, temp);
return ar;
}
As far as I can see, you are not changing the position of pivot element. You are just swapping hi with lo.
At the end, you should place the pivot at its correct position and then return it.
I think there is a danger in the last iteration of your loop.
Suppose you had an array 2,1.
Your pivot is 2.
The lo index increases until it finds an element above the pivot of 1 or meets hi.
In this case it will meet hi and both lo and hi will be pointing to 1.
At this point your partition function returns and reports that the array has been partitioned into:
{2},{1}
But this is incorrect because 1 is < than the pivot so should have been in the first partition.
Perhaps when lo meets hi you should perform an extra test to see whether the element at hi should be included in the left or right partition?
Related
I understand the concept of quicksort, and most of the implementations I have seen largely make sense to me. However the one by Robert Sedgewick does not, his implementation is along the lines of:
private void sort(int lo, int hi) {
if (hi <= lo) return;
int j = partition(lo, hi);
sort(lo, j-1);
sort(j+1, hi);
}
private int partition(int lo, int hi) {
int start = lo;
int partition = hi + 1;
int pivot = theArray[lo];
while (true) {
// left to right
while (less(theArray[++start], pivot))
if (start == hi) break;
// right to left
while (less(pivot, theArray[--partition]))
if (partition == lo) break;
// check if pointers cross
if (start >= partition) break;
exch(theArray, start, partition);
}
// place pivot at partition
exch(theArray, lo, partition);
return partition;
}
private boolean less(int v, int w) {
return v < w;
}
private void exch(int[] a, int i, int j) {
int swap = a[i];
a[i] = a[j];
a[j] = swap;
}
I understand all of it except for the partition function. Particularly what happens in the while(true) portion
Let's make some "brain debug"
int pivot = theArray[lo];
Fixes 1st element as pivot value
while (less(theArray[++start], pivot))
if (start == hi) break;
Finds the first element for exchange - the leftmost one bigger than pivot
while (less(pivot, theArray[--partition]))
if (partition == lo) break;
Finds the second element for exchange - the rightmost one less than pivot
if (start >= partition) break;
Breaks while (true) loop when there is no more elements to exchange
exch(theArray, start, partition);
Exchanges elements found
Example [5 3 8 9 2]:
5 is pivot
first 'bad' element is 8
second one is 2
they are exchanged
[5 3 2 9 8]
now first index stops at 4 (hi), second index stops at 2, loop breaks, and 5 exchanges with 2.
Result: [2 3 5 9 8]
Subranges (2,3,5) and (9,8) are new partitions
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.
I want to modify QuickSort (in Java) so that every time Partition is called, the median of the proportioned array is used as the pivot.
I have a median selection algorithm in Java that returns the kth smallest element, in this case the median. I have tons of quicksort algorithms in java that all work by themselves and sort an array. Unfortunately I can't combine those two in order to achieve the above... Everytime I try it i usually get stackoverflow erros.
Can anybody show me code to see how it can be done?
Thanks
EDIT: For example this is a median selection algorithm that I have tried to use.
public int quickSelect(int[] A, int p, int r, int k) {
if (p==r) return A[p];
int q = Partition(A,p,r);
int len = q-p+1;
if (k == len) return A[q];
else if (k<len) return Select(A,p,q-1,k);
else return Select(A,q+1,r,k-len);
}
public int partition(int[]A, int p, int r) {
int x = A[r];
int i = p-1;
for (int j = p; j<=r-1; j++) {
if (A[j] <= x) {
i++;
swap(A,i,j);
}
}
swap(A,i+1,r);
return i+1;
}
It works by itself but when I try to call quickSelect through quicksort's partition function to return the pivot to be used, it doesn't work. Obviously I'm doing something wrong but I don't know what. Unfortunately on the Internet I haven't found any algorithm, even in pseudocode, that would combine a median selection with quicksort.
The standard way to get the median is to sort the data. And you want to sort the data by partitioning on the median. This seems very chicken and egg to me.
Could you elaborate on why you want to partition/pivot on the median?
What you are looking for is the Selection Algorithm. Here's a link with pseudocode.
From the link:
In computer science, a selection algorithm is an algorithm for finding the kth smallest number in a list
To find the median you want to find the k=floor((n+1)/2) smallest number in the list where n is the size of the list.
Note that in PARTITION the pivot is A[r].
public int QUICKSORT2(int[] A, int p, int r) {
if (p<r) {
int median=Math.floor((p + r) /2) - p + 1
int q=SELECT(A, p, r, median)
q=PARTITION2(A, p, r, q)
QUICKSORT2(A, p, q-1)
QUICKSORT2(A, q+1, r)
}
}
public int PARTITION2(int[]A, int p, int r, int q) {
int temp = A[r];
A[r]=A[q];
A[q]=temp;
return PARTITION(A, p, r)
}
You can use this ...
int Select(int array[],int start, int end,int k){
if(start==end){
return start;
}
int x=array[end];
int i=start-1;
for(int j=start;j<=end-1;j++){
if(array[j]<x){
i++;
Swap(array+i,array+j);
}
}
i++;
Swap(array+i,array+end);
if(i==k){
return i;
}
else if(i>k){
return Select(array,start,i-1,k);
}
else{
return Select(array,i+1,end,k);
}
}
Select will partition array on kth smallest element in array;
I have the following problem I need to solve, but is strugling a bit. Would really appreciate it if someone can help.
In short in comes down to the following:
If the search key is in the array - it returns the smallest index i for which a[i] is equal to the key
If the search key is not in the array but greater - it returns the smallest index i as -i where a[i] is greater than the key
If the search key is not in the array but smaller - it returns -j where j is the index of the last element in the array
I have the code of searching for the key, but I'm not sure how to return the indexes as mentioned above...
import java.util.Arrays;
public class BinarySearchIndex2 {
// a working recursive version
public static int search(String key, String[] a) {
return search(key, a, 0, a.length);
}
public static int search(String key, String[] a, int lo, int hi) {
// possible key indices in [lo, hi)
if (hi <= lo) return -1;
int mid = lo + (hi - lo) / 2;
int cmp = a[mid].compareTo(key);
if (cmp > 0) return search(key, a, lo, mid);
else if (cmp < 0) return search(key, a, mid+1, hi);
else return mid;
}
public static void main(String[] args) {
String key = args[0];
int sizeoflist = StdIn.readInt();
String[] a = new String[sizeoflist];
int counter = 0; //counter for while loop to fill array a
while (!StdIn.isEmpty()){
a[counter] = StdIn.readString();
counter++;
}
Arrays.sort(a); // sort the words (if needed)
if ( search(key, a) < 0) ; /* System.out.println();*/
else if ( search(key, a) > 0 ) ;
else if ( search(key, a) = 0 ) ;
}
}
Would be really glad if someone can help me with this matter...
Thanks!
String.compareTo performs a lexicographical comparison. This means that it can decide that "50">"100", whereas clearly 50<100 is what you would expect. This will affect your Arrays.sort call so your Array is already messed up.
Is it a must to have public static int search(String key, String[] a) as your API?
If you can change it to be public static int search(int key, int[] a) it will make your API work (assuming you don't have any bugs I missed).
Hope this was what you were referring to.
Edit: some fine tuning to the problem analysis.
The important point is here:
if (hi <= lo) return -1;
This occurs when you got an sub-array to search of size zero, which means that the element is not there. Now think: what does the specification say about the return value here?