4

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);

    }
}    
flawr
  • 10,814
  • 3
  • 41
  • 71
  • 1
    Hmm, right after the if (i – matrixanomaly Apr 13 '15 at 16:20
  • @matrixanomaly Thanks! I think that would work, but as far as I could remember I once wrote quicksort without that additional block=/ – flawr Apr 13 '15 at 16:34
  • Just posted the correct answer, it doesn't have an additional if block! – matrixanomaly Apr 13 '15 at 17:07
  • The condition for `j` in `while(j>0 && a[j]>v)` is wrong. Should be `j>left`. – CiaPan Apr 22 '16 at 09:21
  • @CiaPan Oh my god, thank you so much! I didn't expect to see my mistake after more than a year, feel free to add it as an answer so I can show my appreciation by upvoting=) – flawr Apr 22 '16 at 09:24
  • @flawr Glad to help. Anyway, I didn't mean it 'an answer' – I have not studied the code thoroughly, just spotted a suspicious piece and reported it. So, even if this tiny fix cures your actual problem, I'm going to leave it here, just as a comment. – CiaPan Apr 28 '16 at 06:59
  • @CiaPan Well then, thank you anyway, I'm really glad to finally have found this mistake! – flawr Apr 28 '16 at 12:00

3 Answers3

3

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.

Paul Warnick
  • 903
  • 2
  • 13
  • 26
  • 2
    This is not gonna solve the issue: The first while loop will terminate as the pivot is the right most element, and the second one will terminate because of the additional condition. Adding the suggested line also will not change anything, as in this case the following 'if' block will fail, and after the do-while condition will faill too, so it is unnecessary. – flawr Apr 13 '15 at 16:32
  • That's a really good point actually, I'm going to edit my answer. – Paul Warnick Apr 13 '15 at 16:37
2

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 :)

Community
  • 1
  • 1
matrixanomaly
  • 6,627
  • 2
  • 35
  • 58
  • Thank you very much! So my main mistake was that I fogot to increment after swapping, right? (And I was just asking myself why you print `w`s in your `main`, do you perhaps mean `b[w]` instead? `Arrays.toString(b)` would be an alternative in this case=) – flawr Apr 13 '15 at 17:36
  • @flawr oh no. i missed out the w. And thanks for the arrays.toString method! – matrixanomaly Apr 13 '15 at 17:45
  • @flawr I've fixed my code, please take a look at it again, I made an embarassing mistake earlier lol. And yes your main mistake was that you forgot to increment, and later to check if the subarray has been sorted or not before calling a sort again, hence the infinite loop. – matrixanomaly Apr 13 '15 at 18:46
  • Ok now it works I tested it with tons of test cases, thank you so very much! (Cannot upvote more than once=) One quick note: You now use `b.toString()` which does not work (only returns adress), use `Arrays.toString(b)` instead=) – flawr Apr 13 '15 at 19:43
  • Yep you're welcome! Oh yeah I made that edit in ideone but forgot to reflect it here haha – matrixanomaly Apr 13 '15 at 20:09
0

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;
}