4

I thought I had a good understanding of how quicksort works, until I watched the vid on http://code.google.com/edu/algorithms/index.html where Jon Bentley introduced his "beautiful quicksort code", which is as follows:

void quicksort(int l, int u){
    int i, m;
    if(l >= u) return;
    m = l;
    for(i = l+1; i<= u; i++)
        if(x[i] < x[l])
            swap(++m, i); //this is where I'm lost. Why the heck does it preincrement?

    swap(l, m);

    quicksort(l, m-1);
    quicksort(m+1, u);

}

Another part of the algo that confuses me is the final swap after the FOR loop. Why is that neccessary? Lets assume that the array is already in order. If that is true, no swaps will occur since x[i] > x[l]. At the end, we swap l with m. That screws up the order.

Am I missing something?

Thanks.

Chris Gerken
  • 16,221
  • 6
  • 44
  • 59
Kira
  • 549
  • 8
  • 24
  • 1
    I guess the URL above is broken now, would be nice if you can provide the latest URL if possible. – Phalgun Aug 13 '19 at 09:18

5 Answers5

8

On the beginning m is set to l, and the element x[l] is chosen to be partitioning element (pivot).

Next, the algorithm iterates over a list and, whenever it finds an element less than x[l], it moves it just after the current m. That means, when m > l, then elements on all positions from l+1 to m, are lesser than element x[l].

For example:

3 5 4 2 1  l = 0, m = 0, i = 1
  ^
3 5 4 2 1  l = 0, m = 0, i = 2
    ^
3 2 4 5 1  l = 0, m = 1, i = 3
      ^
3 2 1 5 4  l = 0, m = 2, i = 4
        ^

and at the end, we swap the last of the smaller numbers with the first (partitioning) element to get:

1 2 3 5 4

If there are no smaller elements than the first one, the swap does nothing (because m is equal to l).

Rafał Rawicki
  • 22,324
  • 5
  • 59
  • 79
  • This is a very concise and estethic implementation, although it is hard to understand when you are looking at it for few seconds. Usually a good method for understanding this type of algorithms is to try writing loop invariants (it helps if you had verification using Hoare's logic in college) – Rafał Rawicki Apr 06 '12 at 21:53
  • My second remark: This is not a very good quicksort implementation. Sorting algoritms often happen to receive partially sorted data. For this implementation partially or the completely sorted data are the worst cases. The quality of quicksort implementation lies in wise choice of partitioning element. – Rafał Rawicki Apr 06 '12 at 22:02
  • Wow, that's a very clear explanation. It makes a lot more sense now, since we are swapping elements < x[l] with m, everything before m will be < x[l]. And since m < x[l], the final swap completes the sort. Interesting. – Kira Apr 07 '12 at 05:00
  • One issue though : shouldn't the final swap give you 1 2 3 5 4? after the second swap m = 2. So swapping m and l, which are 2 and 3, respectively, will give us 1 2 3 5 4, not 2 1 3 5 4. – Kira Apr 07 '12 at 05:00
  • @RafałRawicki yeah,when the elements of array are all same,the time complexity of this implementation will deteriorate into `O(n^2)` – kajibu Jan 26 '21 at 00:24
1

The element at x[l] is the chosen pivot. The invariant of the for loop is that all elements x[l+1] through x[m] are less than the pivot, and all elements from x[m] through x[i] are bigger or equal to the pivot.

When it finds an element less than the pivot, it moves it down to entry m+1, then bumps up m. (The entry at m+1 was bigger than the pivot, and thus moving it up is fine.)

The last swap is to move the pivot from x[l] to x[m] because it needs to end up between the lower array and the upper array. If no swaps happen (sorted array example), then m==l and the final swap doesn't move anything.

The code would be clearer pedagogically to set m = l + 1 and use m++ instead of ++m.

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
1

The partition algorithm is good and easy to remember. It has been in Communications of ACM or retold by Benltey multiple time. It also appears in Programming Pearls book by Bentley. The idea is to keep track of the element which negates the post condition i.e elements behind pivot are less and above in index are greater. But, if picking the random element is not random,we may end up with the largest (or smallest) element which will leave us with doing a lot more swaps to the O(n). An implementation and explanation in java is [blog]: http://harisankar-krishnaswamy.blogspot.in/2013/05/quick-sort-partition-algorithm.html "here"

Hari

0

If the array is sorted, m never changes from its initial value of l, so the swap does nothing.

user207421
  • 305,947
  • 44
  • 307
  • 483
0

Fixed indices l and u point to the first and last elements of the sub-array to be sorted. The value x[l] is always chosen as the pivot.

At the top of the loop, the sub-array (excluding the pivot x[l]) is divided as follows:

  • lower region: elements with indices l < index <= m have been tested and found to be < x[l]
  • middle region: elements with indices m < index < i have been tested and found to be >= x[l]
  • upper region: elements with indices i <= index <= u have not been tested yet

One possible source of confusion is that, while the loop is running, the region with values greater than the pivot is the middle section, not the upper one. It does not reach the upper part of the array until the untested region is exhausted -- it is effectively "moved", by repeated swaps, to make room for an expanding lower region.

Specifically, loop variable m is pre-incremented whenever the lower region needs expanding: the first element of the middle region (if any) must be moved to the end, in order to expand the lower region. Note that, if the middle region is empty, m == i after the pre-increment (for this case, the swap() operation must be a no-op).

At the end, the pivot x[l] is swapped to the end of the lower region, to put it in place for the recursive step. Note that, if the array is already in order, the lower region is empty, m == l, and the final swap() operation is again a no-op.

comingstorm
  • 25,557
  • 3
  • 43
  • 67