-1

So I was reading the Hoare's partition part of the Quicksort wiki and it says:

With respect to this original description, implementations often make minor but important variations. Notably, the scheme as presented below includes elements equal to the pivot among the candidates for an inversion (so "greater than or equal" and "less than or equal" tests are used instead of "greater than" and "less than" respectively; since the formulation uses do...while rather than repeat...until which is actually reflected by the use of strict comparison operators

I read through the wiki section and the original paper but I'm not sure how to implement this original method. The part of the original method that I'm not sure about is:

Hoare therefore stipulates that at the end, the sub-range containing the pivot element (which still is at its original position) can be decreased in size by excluding that pivot, after (if necessary) exchanging it with the sub-range element closest to the separation; thus, termination of quicksort is ensured.

The history section of the wiki article states:

Hoare learned about ALGOL and its ability to do recursion that enabled him to publish an improved version of the algorithm in ALGOL.

It seems like the idea is to swap the pivot into place and then not include the pivot in the following partition steps.

Question: Is the pivot swapped into place using this method?

rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • 1
    You have described your state of mind. But what is your _programming question?_ – matt Aug 11 '23 at 21:29
  • Also, is this really any different from e.g. https://stackoverflow.com/questions/63623606/can-someone-explain-hoares-partitioning-scheme-to-me ? – matt Aug 11 '23 at 21:31
  • I don't quite understand your `how do we know where the actual pivot value is` question. It seems to me that the location of the pivot element is the one thing that we know for certain throughout each partitioning step. – 500 - Internal Server Error Aug 11 '23 at 21:42
  • @matt yeah im asking about original hoare's implementation – Reggie Hurley Aug 11 '23 at 21:54
  • @500-InternalServerError with hoare's partition we aren't guaranteed pivot location afterwards – Reggie Hurley Aug 11 '23 at 21:54
  • "It seems like the idea is to swap the pivot into a spot and then not include that index in the next sort, but how do we know where the actual pivot value is when using this method." - it's... in the spot that it was swapped into? Why is there an issue? – Karl Knechtel Aug 16 '23 at 03:31
  • Qt/Thread mentioned on '[Meta](https://meta.stackoverflow.com/q/426035/3799241)', follow up a bit quickly on Feedback, just saying... @OP... :idea: – chivracq Aug 16 '23 at 03:42
  • @KarlKnechtel - in the original paper, the pivot value only has to be within range of the keys, and does not have be equal to any of the keys, and the pivot value is not put into place. At the end of a partition step, there is a dividing line, left of the line are values <= pivot, right of the line values >= pivot. – rcgldr Aug 16 '23 at 06:08

1 Answers1

2

Notably, the scheme as presented below includes elements equal to the pivot among the candidates for an inversion (so "greater than or equal" and "less than or equal" tests are used instead of "greater than" and "less than" respectively ...)

To summarize the Wiki version, it may swap elements equal to the pivot or the pivot itself. This means elements equal to the pivot or the pivot itself can end up anywhere and that no elements can be excluded from later partition steps. The advantage of this is that it eliminates the need for boundary checks. Most current implementations of Hoare partition scheme use this method.

Is the pivot swapped into place using this method?

In the original paper, normally the pivot is not swapped into place. After a partition step, there is a dividing line, values to the left are <= pivot and values to the right are >= pivot, the same as the Wiki version. There is one exception when the partitioning runs into the left or right boundary, in which case one element, which may or may not be the pivot, is swapped into place next to the dividing line and excluded from later partition steps.

Swapping the pivot into place was implemented with an improved version written in ALGOL. The boundary handling was also improved.

Tony Hoare's ALGOL code for partition is algorithm 63 and quicksort is algorithm 64 on page 321 in this snippet of four pages from an ACM article:

https://dl.acm.org/doi/pdf/10.1145/366622.366642

ALGOL is a pass by reference language, so partition() changes the values of I and J. F is the index to the pivot, and X is the value of the pivot.

The code does not swap elements equal to the pivot or the pivot itself, leaving the pivot in its original position. At the end of partition(), there is code that swaps the pivot into place, and then excludes it:

if I < F then begin exchange (A[I], A[F]);
        I := I + 1;
    end
if F < J then begin exchange (A[F], A[J]);
        J := J - 1;
    end

The returned values for I and J will exclude the pivot element. The disadvantage of this method is boundary checks (the for loops) are needed.


I converted the ALGOL algorithm to a C++ version, with the partition logic as part of the quicksort function, sorting 64 bit unsigned integers instead of floats. With pseudo-random data, after swapping pivot into place, about half the time J == (I-1) and the other half J == (I-2). In the case of all equal elements, I is incremented to N, J is decremented to M, and the quicksort is completed in one partition step.

static void QuickSort(uint64_t A[], int M, int N)
{
uint64_t P;                             // pivot value
int L;                                  // pivot index
int I, J;
    if(M >= N)
        return;
    L = (M+N)/2;                        // Hoare used random value
    P = A[L];
    I = M;
    J = N;
    while(1){                           // partition step 
        while(I < N && A[I] <= P)       //  scan for A[I] > pivot
            I++;
        while(J > M && A[J] >= P)       //  scan for A[J] < pivot
            J--;
        if(I >= J)                      //  break if step done
            break;
        std::swap(A[I], A[J]);          //  swap elements
        I++;                            //  advance indexes
        J--;
    }
    if(I < L){                          // swap pivot into place
        std::swap(A[I], A[L]);
        I++;
    } else if(L < J){
        std::swap(A[L], A[J]);
        J--;
    }
    QuickSort(A, M, J);                 // quicksort left  side
    QuickSort(A, I, N);                 // quicksort right side
}
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • Thanks for the great answer! One question: isn't there a good chance that the pivot will have already swapped and be in a different position if one of the scanning pointers reaches a boundary? – Reggie Hurley Aug 12 '23 at 18:27
  • @ReggieHurley - I updated your question and my answer. The original paper version scans left to right for an element > pivot (not >=) and scans right to left for an element < pivot (not <=), so the pivot and elements == pivot are skipped over in the scans and are not swapped. The pivot is not swapped into place. If one of the pointers reaches a boundary, then one element, which may or may not be the pivot, is swapped into place and excluded from later partition steps. – rcgldr Aug 16 '23 at 22:58
  • @ReggieHurley - the ALGOL code is an improved version that does swap the pivot into place at the end of a partition step. It also handles one or both pointers (indexes) reaching a boundary better. My answer includes C++ code based on the ALGOL version. I also updated the wiki article to include links to the original paper and to the ALGOL code. – rcgldr Aug 16 '23 at 23:13