Good of you to include an explicit question, if a compound one.
Is this algorithm good…?
This is quicksort, using Hoare's partition scheme with a twist (use two reads and two writes to resolve one inversion wrt. pivot instead of a swap (/"exchange" - making it the counterexample to labelling quicksort "partition-exchange sort": a direct exchange is not essential (partition is)), conventionally taken to be equivalent to three reads&writes, each. With today's memory hierarchies, don't expect it to be any faster because of this).
This is a respected algorithm in wide use, if with three-way-partition, even dual pivot values.
(There is a bug in your implementation: stuck if both i
and j
index an element equal to the pivot value (num
).)
Can I improve [quicksort with minimised reads&writes in partition]?
For readability, you can and should separate the concerns of picking a pivot index, partition, and sort.
Of the Implementation issues
mentioned in the wikipedia article, two reduce the likelihood and severity of worst case behaviour:
- don't pick a value close to the beginning or end of the range as a pivot - this is a bad choice with ordered input (even almost and/or reverse ordered).
- recurse on the partitions from smallest to largest - this should limit the growth in call stack depth to logarithmic in the number of items to sort thanks to "tail call"/"tail recursion" optimisation. (If it doesn't, options include picking a pivot that guarantees a favourable partition, or turning the call for the largest partition into iteration.)
(Getting late: the following code is work in progress; posting this to save the above, mainly (not quite trusting SE's autosave)(Never used C# - give me a break on documentation comments, const-correctness, commendable use of static
or some such.))
static int pivotIndex(int left, int right)
{
int n = right - left;
return left + n/2;
}
/** partition int array a from start to end, exclusive
* @returns -1 if known sorted, else (one) index of pivot */
static int partition(int[] a, int left, int right, int pivotIndex// = left
) {
int l = left,
r = right;
int pivot = a[pivotIndex]; // a[pivotIndex] available for storage
a[pivotIndex] = a[l]; // a[l] available for storage
for (;;) {
do
if (--r <= l) {
a[l] = pivot;
return l;
}
while (pivot <= a[r]);
a[l] = a[r]; // a[r] available for storage
do
if (r <= ++l) {
a[r] = pivot;
return r;
}
while (a[l] <= pivot);
a[r] = a[l]; // a[l] available for storage
}
}
/** sort int array a from start to end, exclusive */
static void QuickSort(int[] a, int start, int end) {
int pivotIndex = partition(a, start, end,
pivotIndex(start, end));
if (start < pivotIndex) {
QuickSort(a, start, pivotIndex);
// QuickSort(a, pivotIndex + 1, end);
} // else
if (start <= pivotIndex)
QuickSort(a, pivotIndex + 1, end);
}
int num = a[start]
could yield better performance (I believe most QS implementations choose the center element, which does excellently for already-sorted [or near sorted] data). \$\endgroup\$a[i] = a[j]; while (i < j && a[i] < num) i++; a[j] = a[i];
instead of the conventional swap? Does this code work, as required for CR? (It almost does: the minimal change required is the inclusion of values equal to the pivot in at least one of the conditions in the loop.) \$\endgroup\$