From 5d6ea0e778aea1b291a716de1f531436a660410b Mon Sep 17 00:00:00 2001 From: Richard Thier Date: Mon, 5 May 2025 22:52:20 +0200 Subject: [PATCH] added three-way single pass partitioning codes, they are a bit slower than the two-pass 3-way partitioning on my machine, but sp2 gets very close in performance so might be tweaked to get it be better maybe --- qs.c | 32 ++++++++++ qsort.h | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- zssort.h | 53 ++++++++++++++++ 3 files changed, 262 insertions(+), 8 deletions(-) diff --git a/qs.c b/qs.c index 94aa11c..da065cb 100644 --- a/qs.c +++ b/qs.c @@ -72,10 +72,42 @@ void qs3() { printArray(data, n); } +void qs3_sp() { + #include "data.inc" + + int n = sizeof(data) / sizeof(data[0]); + + // memory junnnk is enough + rpivotstate rand; + + // perform qs3 on data + quicksort_rand3_sp(data, 0, n - 1, &rand); + + printf("(qs3_sp) Sorted array in ascending order: \n"); + printArray(data, n); +} + +void qs3_sp2() { + #include "data.inc" + + int n = sizeof(data) / sizeof(data[0]); + + // memory junnnk is enough + rpivotstate rand; + + // perform qs3 on data + quicksort_rand3_sp2(data, 0, n - 1, &rand); + + printf("(qs3_sp2) Sorted array in ascending order: \n"); + printArray(data, n); +} + int main() { qs(); qsr(); zss(); qs3(); + qs3_sp(); + qs3_sp2(); return 0; } diff --git a/qsort.h b/qsort.h index dc32535..a935535 100644 --- a/qsort.h +++ b/qsort.h @@ -2,6 +2,11 @@ #ifndef MY_QUICKSORT_H #define MY_QUICKSORT_H +/* Used in quicksort_rand3_sp under which we call regular quicksort_rand3 without singlepassing */ +#ifndef RAND3_SP_VAL +#define RAND3_SP_VAL 64 +#endif + #include /* Uncomment for debugging with if(someevent) raise(SIGINT); */ @@ -97,22 +102,19 @@ typedef struct pret3 pret3; * @returns The partition points, end of left and right (inclusive) */ static inline pret3 partition3(uint32_t array[], int low, int high, uint32_t pivotval) { - /* select the rightmost element as pivot */ - uint32_t pivot = pivotval; - /* index until smaller or eq elements lay */ int i = (low - 1); uint32_t pc = 0; /* traverse each element of the array */ - /* compare them with the pivot */ + /* compare them with the pivotval */ #pragma GCC unroll 4 for (int j = low; j <= high; ++j) { /* Branchless pivot-count */ - pc += (array[j] == pivot); + pc += (array[j] == pivotval); - if(array[j] < pivot) { - /* if element smaller than pivot is found */ + if(array[j] < pivotval) { + /* if element smaller than pivotval is found */ /* swap it with the greater element pointed by i */ ++i; @@ -134,7 +136,7 @@ static inline pret3 partition3(uint32_t array[], int low, int high, uint32_t piv #pragma GCC unroll 4 for (int j = high; j > i; --j) { - if (array[j] > pivot) { + if (array[j] > pivotval) { /* if element smaller than pivot is found */ /* swap it with the greater element pointed by i */ --i2; @@ -151,6 +153,138 @@ static inline pret3 partition3(uint32_t array[], int low, int high, uint32_t piv return ret; } +/** + * Partition the array threeway by puutting ALL pivots to the middle and smaller/biggers left-right. + * Single-pass version. + * + * @param array The array to partition + * @param low From when. (inclusive) + * @param high Until when. (inclusive too!) + * @param pivotval This value is used to partition the array. + * @returns The partition points, end of left and right (inclusive) + */ +static inline pret3 partition3_sp(uint32_t array[], int low, int high, uint32_t pivotval) { + + /* Invariant for left: index until smaller (than pivot) elements lay */ + int il = (low - 1); + /* Invariant for right: index until (from top) bigger elements lay */ + int ir = (high + 1); + /* Indices from where we swap left and right into "is" (and sometimes swap among here too) */ + int jl = low; + int jr = high; + + while(jl <= jr) { + /* Jump through any pivot elements */ + while((array[jl] == pivotval) && (jl < high)) ++jl; + while((array[jr] == pivotval) && (jr > low)) --jr; + if(jl > jr) break; + + if(array[jl] < pivotval) { + /* if element smaller than pivotval is found */ + /* swap it with the element pointed by i */ + ++il; + if(il != jl) swapit(&array[il], &array[jl]); + } else { + /* Here its sure a[jl] and a[jr] are not pivots */ + /* Which means array[jl] > pivotval and on left */ + + if(array[jr] < pivotval) { + /* We can totally safely just swap them */ + /* Because both are in wrong places.... */ + swapit(&array[jl], &array[jr]); + /* swap it with the element pointed by i */ + ++il; + if(il != jl) swapit(&array[il], &array[jl]); + } else { + /* Left->right move needed, but right is */ + /* at good place already so handle that! */ + /* ensured: if(array[jr] > pivotval) */ + + /* Progress invariants */ + /* swap with the element pointed by i */ + --ir; + if(ir != jr) swapit(&array[ir], &array[jr]); + /* Because of swap we might swaped in pivot! */ + /* So continue our loop but do not step jl! */ + /* ++jl; // incomplete so no step! */ + --jr; /* STEP RIGHT because "continue"! */ + continue; + } + } + /* Step left being processed */ + ++jl; + + if(array[jr] > pivotval) { + /* if element bigger than pivotval is found */ + /* swap it with the element pointed by i */ + --ir; + if(ir != jr) swapit(&array[ir], &array[jr]); + } else { + /* On wrong side - so we just need to look */ + /* for left-wrong-sided to swap it with! */ + continue; /* I think no ++jl needed! */ + } + /* Step right being processed */ + --jr; + } + + /* return the partition points */ + pret3 ret; + ret.leftend = il; + ret.rightend = ir; + return ret; +} + +/** + * Partition the array threeway by puutting ALL pivots to the middle and smaller/biggers left-right. + * Single-pass version 2.0 + * + * @param array The array to partition + * @param low From when. (inclusive) + * @param high Until when. (inclusive too!) + * @param pivotval This value is used to partition the array. + * @returns The partition points, end of left and right (inclusive) + */ +static inline pret3 partition3_sp2(uint32_t array[], int low, int high, uint32_t pivotval) { + + /* Invariant for left: index until smaller (than pivot) elements lay */ + int il = (low - 1); + /* Invariant for right: index until (from top) bigger elements lay */ + int ir = (high + 1); + /* Indices from where we swap left and right into "is" (and sometimes swap among here too) */ + int jl = low; + int jr = high; + + while(jl <= jr) { + /* Handle left and find wrongly placed element */ + while((array[jl] <= pivotval) && (jl <= jr)) { + if(array[jl] != pivotval) { + if(++il != jl) + swapit(&array[il], &array[jl]); + } + ++jl; + } + + /* Handle right and find wrongly placed element */ + while((array[jr] >= pivotval) && (jl <= jr)) { + if(array[jr] != pivotval) { + if(--ir != jr) + swapit(&array[ir], &array[jr]); + } + --jr; + } + + /* Swap the two found elements that are wrongly placed */ + if(jl < jr) swapit(&array[jl], &array[jr]); + } + + /* return the partition points */ + pret3 ret; + ret.leftend = il; + ret.rightend = ir; + return ret; +} + /* RANDOMPIV */ /** @@ -225,6 +359,41 @@ static inline void quicksort_rand3(uint32_t array[], int low, int high, rpivotst } } +/** Randomized pivoting in-place recursive quicksort on array for elements in [low, high] indices */ +static inline void quicksort_rand3_sp(uint32_t array[], int low, int high, rpivotstate *state) { + if (low < high) { + /* partition threeway by random pivot */ + int pi = pick_pivot(state, (high + 1) - low) + low; + pret3 res; + if(high - low > RAND3_SP_VAL) res = partition3_sp(array, low, high, array[pi]); + else res = partition3(array, low, high, array[pi]); + + /* recursive call on the left of pivot */ + quicksort_rand3_sp(array, low, res.leftend, state); + + /* recursive call on the right of pivot */ + quicksort_rand3_sp(array, res.rightend, high, state); + } +} + +/** Randomized pivoting in-place recursive quicksort on array for elements in [low, high] indices */ +static inline void quicksort_rand3_sp2(uint32_t array[], int low, int high, rpivotstate *state) { + if (low < high) { + /* partition threeway by random pivot */ + int pi = pick_pivot(state, (high + 1) - low) + low; + pret3 res; + res = partition3_sp2(array, low, high, array[pi]); + /* if(high - low > RAND3_SP_VAL) res = partition3_sp2(array, low, high, array[pi]); + else res = partition3(array, low, high, array[pi]); */ + + /* recursive call on the left of pivot */ + quicksort_rand3_sp2(array, low, res.leftend, state); + + /* recursive call on the right of pivot */ + quicksort_rand3_sp2(array, res.rightend, high, state); + } +} + /* MINMAX */ /** diff --git a/zssort.h b/zssort.h index 4ef2029..d0de2d2 100644 --- a/zssort.h +++ b/zssort.h @@ -89,6 +89,59 @@ static inline void zssort_rand3(uint32_t array[], int low, int high, rpivotstate } } +/** Always at most log(n) space needing randomized quicksort variant */ +static inline void zssort_rand3_sp(uint32_t array[], int low, int high, rpivotstate *state) { + while (low < high) { + int pi = pick_pivot(state, (high + 1) - low) + low; + pret3 res; + if(high - low > RAND3_SP_VAL) res = partition3_sp(array, low, high, array[pi]); + else res = partition3(array, low, high, array[pi]); + + /* If we recurse only the smaller part */ + /* That ensures at most n/2 elements can */ + /* be on any given level of the recursion */ + /* tree: that is we ensure log2(N) memuse! */ + if((res.leftend - low) < (high - res.rightend)) { + // Left smaller: recurse left of pivot + zssort_rand3_sp(array, low, res.leftend, state); + // (*) Update partitioning loop for remaining part + low = res.rightend; + } else { + // Right smaller: recurse right of pivot + zssort_rand3_sp(array, res.rightend, high, state); + // (*) Update partitioning loop for remaining part + high = res.leftend; /* high inclusive! */ + } + } +} + +/** Always at most log(n) space needing randomized quicksort variant */ +static inline void zssort_rand3_sp2(uint32_t array[], int low, int high, rpivotstate *state) { + while (low < high) { + int pi = pick_pivot(state, (high + 1) - low) + low; + pret3 res; + res = partition3_sp2(array, low, high, array[pi]); + /*if(high - low > RAND3_SP_VAL) res = partition3_sp(array, low, high, array[pi]); + else res = partition3(array, low, high, array[pi]);*/ + + /* If we recurse only the smaller part */ + /* That ensures at most n/2 elements can */ + /* be on any given level of the recursion */ + /* tree: that is we ensure log2(N) memuse! */ + if((res.leftend - low) < (high - res.rightend)) { + // Left smaller: recurse left of pivot + zssort_rand3_sp2(array, low, res.leftend, state); + // (*) Update partitioning loop for remaining part + low = res.rightend; + } else { + // Right smaller: recurse right of pivot + zssort_rand3_sp2(array, res.rightend, high, state); + // (*) Update partitioning loop for remaining part + high = res.leftend; /* high inclusive! */ + } + } +} + /* ZSSORTC */ /** Always at most log(n) space needing randomized quicksort variant - with checking for sameconst-arrays */