meanqs first version + empty line cleanups; also partitioning with min-max values of partitions selected and partitioning by a given pivot value instead of pivot or pivot index

This commit is contained in:
Richard Thier 2025-04-07 02:38:28 +02:00
parent f15cc423d2
commit 04705b5790
2 changed files with 301 additions and 21 deletions

191
qsort.h
View File

@ -6,10 +6,19 @@
/* Structure:
*
* - BASICS
* - EXTRAS
* - BASICS: Basic quicksort
* - EXTRAS: Extra quicksort and partitioning helpers (also random pick and pivot index picks). Basic (free) min-max search
* - PMINMAX: Partitioning with min-max searches in generated partitions
* - VALPART: Partitioning by value - not by random or given pivot index!
*/
/** Hand-coded abs difference - I wanted to minimize dependencies by not needing stdlib.h */
static inline int64_t absdiff(uint32_t a, uint32_t b) {
/* Should be branchless and fast */
int64_t n = (a - b);
return (n > 0) ? n : -n;
}
/* BASICS */
/** Swap operation */
@ -35,7 +44,7 @@ static inline int partition(uint32_t array[], int low, int high) {
/* select the rightmost element as pivot */
uint32_t pivot = array[high];
/* index until smaller or eq elements lay */
int i = (low - 1);
@ -44,11 +53,10 @@ static inline int partition(uint32_t array[], int low, int high) {
#pragma GCC unroll 4
for (int j = low; j < high; ++j) {
if (array[j] <= pivot) {
/* if element smaller than pivot is found */
/* swap it with the greater element pointed by i */
++i;
/* swap element at i with element at j */
swapit(&array[i], &array[j]);
}
@ -56,7 +64,7 @@ static inline int partition(uint32_t array[], int low, int high) {
/* swap the pivot element with the greater element at i */
swapit(&array[i + 1], &array[high]);
/* return the partition point */
return (i + 1);
}
@ -65,10 +73,10 @@ static inline int partition(uint32_t array[], int low, int high) {
static inline void quicksort(uint32_t array[], int low, int high) {
if (low < high) {
int pi = partition(array, low, high);
/* recursive call on the left of pivot */
quicksort(array, low, pi - 1);
/* recursive call on the right of pivot */
quicksort(array, pi + 1, high);
}
@ -95,7 +103,7 @@ static inline int partition_with_pivot(uint32_t array[], int pi, int low, int hi
* writing the whole out I can tell
* this is still fastests basically.
*/
/* swap pivot with rightmost */
swapit(&array[high], &array[pi]);
/* delegate to previous sol. */
@ -136,11 +144,10 @@ static inline int partition_and_minmax(uint32_t array[], int low, int high, uint
/* Lomuto partitioning */
if (array[j] <= pivot) {
/* if element smaller than pivot is found */
/* swap it with the greater element pointed by i */
++i;
/* swap element at i with element at j */
swapit(&array[i], &array[j]);
}
@ -148,7 +155,7 @@ static inline int partition_and_minmax(uint32_t array[], int low, int high, uint
/* swap the pivot element with the greater element at i */
swapit(&array[i + 1], &array[high]);
/* return the partition point */
return (i + 1);
}
@ -174,7 +181,7 @@ static inline int partition_and_minmax_with_pivot(uint32_t array[], int pi, int
* writing the whole out I can tell
* this is still fastests basically.
*/
/* swap pivot with rightmost */
swapit(&array[high], &array[pi]);
/* delegate to previous sol. */
@ -210,4 +217,162 @@ static inline void quicksort_rand(uint32_t array[], int low, int high, rpivotsta
}
}
/* PMINMAX */
/**
* Partition the array with partition-based min-max search (4 values: 2 per partition) and find the pivot element such that
*
* - Elements smaller than pivot are on left of pivot
* - Elements greater than pivot are on right of pivot
*
* @param array The array to partition
* @param low From when. (inclusive)
* @param high Until when. (inclusive too!)
* @param minout_left OUT: Will be filled with the minimum key of left partition or pivot value when partition empty
* @param maxout_left OUT: Will be filled with the maximum key of left partition or pivot value when partition empty
* @param minout_right OUT: Will be filled with the minimum key of right partition or pivot value when partition empty
* @param maxout_right OUT: Will be filled with the maximum key of right partition or pivot value when partition empty
* @returns The partition point.
*/
static inline int partition_and_pminmax(
uint32_t array[],
int low,
int high,
uint32_t *minout_left,
uint32_t *maxout_left,
uint32_t *minout_right,
uint32_t *maxout_right) {
/* This is "Lomuto"s unidirectional partitioner - see algorithms book */
/* select the rightmost element as pivot */
uint32_t pivot = array[high];
*minout_left = pivot;
*maxout_left = pivot;
*minout_right = pivot;
*maxout_right = pivot;
/* index until smaller or eq elements lay */
int i = (low - 1);
/* traverse each element of the array */
/* compare them with the pivot */
#pragma GCC unroll 4
for (int j = low; j < high; ++j) {
/* Lomuto partitioning */
if (array[j] <= pivot) {
/* Branchless min-max */
*minout_left = array[j] < *minout_left ? array[j] : *minout_left;
*maxout_left = array[j] > *maxout_left ? array[j] : *maxout_left;
/* if element smaller than pivot is found */
/* swap it with the greater element pointed by i */
++i;
/* swap element at i with element at j */
swapit(&array[i], &array[j]);
} else {
/* Branchless min-max */
*minout_right = array[j] < *minout_right ? array[j] : *minout_right;
*maxout_right = array[j] > *maxout_right ? array[j] : *maxout_right;
}
}
/* swap the pivot element with the greater element at i */
swapit(&array[i + 1], &array[high]);
/* return the partition point */
return (i + 1);
}
/**
* Partition the array with partition-based min-max search (4 values: 2 per partition) and using the pivot index
*
* - Elements smaller than pivot are on left of pivot
* - Elements greater than pivot are on right of pivot
*
* @param array The array to partition
* @param pi The index of the pivot element to use. 0 or high is what OG quicksorts do.
* @param low From when. (inclusive)
* @param high Until when. (inclusive too!)
* @param minout OUT: Will be filled with the minimum key
* @param maxout OUT: Will be filled with the maximum key
* @returns The partition point.
*/
static inline int partition_and_pminmax_with_pivot(
uint32_t array[],
int pi,
int low,
int high,
uint32_t *minout_left,
uint32_t *maxout_left,
uint32_t *minout_right,
uint32_t *maxout_right) {
/*
* Rem.: This looks like overhead,
* but after seriously considering
* writing the whole out I can tell
* this is still fastests basically.
*/
/* swap pivot with rightmost */
swapit(&array[high], &array[pi]);
/* delegate to previous sol. */
return partition_and_pminmax(array, low, high, minout_left, maxout_left, maxout_left, maxout_right);
}
/* VALPART */
/**
* Partition the array using pivot value - and find pivot closest to that value (and place them at proper pivot index)
*
* - Elements smaller than pivot are on left of pivot
* - Elements greater than pivot are on right of pivot
* - The "pivot" element we have found is the closest to the "pivotval" given.
*
* @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 - pivot will be the closest to this valued element!
* @returns The partition point.
*/
static inline int partition_with_pivotval(uint32_t array[], int low, int high, uint32_t pivotval) {
/* This is "Lomuto"s unidirectional partitioner - see algorithms book */
/* Select the rightmost element as pivot just because */
/* Need some start-value for min(abs(pv - p)) search! */
int64_t mindiff = absdiff(array[low], pivotval);
/* Index of currently found pivot value */
uint32_t pivoti = low;
/* index until smaller or eq elements lay */
int i = (low - 1);
/* traverse each element of the array */
/* compare them with the pivotval */
/* The "<=" is needed for our trickz here too */
#pragma GCC unroll 4
for (int j = low; j <= high; ++j) {
/* This "<=" ensures pivoti must be only searched among "left" values! */
if (array[j] <= pivotval) {
/* if element smaller than pivot is found */
/* swap it with the greater element pointed by i */
++i;
/* swap element at i with element at j */
swapit(&array[i], &array[j]);
/* After this, array[i] can never change - so we can save it as a found pivot-index */
/* Min-search on elements by telling which is closest to pivotval by abs difference! */
int64_t diff = absdiff(array[i], pivotval);
pivoti = (diff < mindiff) ? i : pivoti;
}
}
/* swap the pivot element into its place */
swapit(&array[i], &array[pivoti]);
/* return the partition point: index of pivot element */
return i;
}
#endif /* MY_QUICKSORT_H */

131
zssort.h
View File

@ -1,9 +1,18 @@
#ifndef ZS_SORT_H
#define ZS_SORT_H
/*
* Structure:
* - ZSSORT: The at most log(n) space needing quicksort
* - RZSSORT: Randomized pivoting variant at most log(n) space needing quicksort
* - RZSSORTC: Added check for fully same value const array (also randomized pivot)
* - MEANQS: Randomized pivoting AND pivoting based on min-max range - dual recursive
*/
#include <stdint.h>
#include "qsort.h"
/* ZSSORT */
/** Always at most log(n) space needing quicksort variant */
static inline void zssort(uint32_t array[], int low, int high) {
/* (*) Loop handles original "other half recursion"! */
@ -28,6 +37,8 @@ static inline void zssort(uint32_t array[], int low, int high) {
}
}
/* ZSSORT */
/** Always at most log(n) space needing randomized quicksort variant */
static inline void zssort_rand(uint32_t array[], int low, int high, rpivotstate *state) {
while (low < high) {
@ -52,6 +63,8 @@ static inline void zssort_rand(uint32_t array[], int low, int high, rpivotstate
}
}
/* ZSSORTC */
/** Always at most log(n) space needing randomized quicksort variant - with checking for sameconst-arrays */
static inline void zsrc(uint32_t array[], int low, int high, rpivotstate *state) {
if (low < high) {
@ -66,14 +79,116 @@ static inline void zsrc(uint32_t array[], int low, int high, rpivotstate *state)
}
}
/* MEANQS */
/**
* Rangsort entry point and outer-recursive function of meanqs.
*
* We pick a random pivot, but in the meantime try to quess mean for next level partitioning.
* This and meanqs_inner is co-recursive (call each other)
*
* @param array The array to sort
* @param low From when. (inclusive)
* @param high Until when. (inclusive too!)
* @param state The random pivot picking state
*/
static inline void meanqs(uint32_t array[], int low, int high, rpivotstate *state);
/**
* Inner-recursive function of meanqs - calls meanqs() as next level recursion.
*
* @param array The array to sort
* @param low From when. (inclusive)
* @param high Until when. (inclusive too!)
* @param pivotval We first partition to smaller-eq/bigger than pivotval, later we might pick random though
* @param state The random pivot picking state
*/
static inline void meanqs_inter(uint32_t array[], int low, int high, uint32_t pivotval, rpivotstate *state) {
/* First: partition using pivot val + recurse */
if (low < high) {
/* For the first level, use the pivotval they provide */
/* This only helps somewhat, because below loop.... */
/* ..But code it better if you can do consistently! */
int pi = partition_with_pivotval(array, low, high, pivotval);
/* 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((pi - low) < (high - pi)) {
// Left smaller: recurse left of pivot
meanqs(array, low, pi - 1, state);
// (*) Update partitioning loop for remaining part
low = pi + 1;
} else {
// Right smaller: recurse right of pivot
meanqs(array, pi + 1, high, state);
// (*) Update partitioning loop for remaining part
high = pi - 1; /* high inclusive! */
}
}
/* Later: picking random pivot and partition like that */
/* (*) Rem.: The above if can be understood as different first iteration of this while loop... */
while (low < high) {
/* Random picking here - no info anymore about mean */
int pi = pick_pivot(state, (high + 1) - low) + low;
pi = partition_with_pivot(array, pi, low, high);
/* 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((pi - low) < (high - pi)) {
// Left smaller: recurse left of pivot
meanqs(array, low, pi - 1, state);
// (*) Update partitioning loop for remaining part
low = pi + 1;
} else {
// Right smaller: recurse right of pivot
meanqs(array, pi + 1, high, state);
// (*) Update partitioning loop for remaining part
high = pi - 1; /* high inclusive! */
}
}
}
/**
* Rangsort entry point and outer-recursive function of meanqs.
*
* We pick a random pivot, but in the meantime try to quess mean for next level partitioning.
* This and meanqs_inner is co-recursive (call each other)
*
* @param array The array to sort
* @param low From when. (inclusive)
* @param high Until when. (inclusive too!)
* @param state The random pivot picking state
*/
static inline void meanqs(uint32_t array[], int low, int high, rpivotstate *state) {
if (low < high) {
uint32_t lmin, lmax, rmin, rmax;
int pi = pick_pivot(state, (high + 1) - low) + low;
pi = partition_and_pminmax_with_pivot(array, pi, low, high, &lmin, &lmax, &rmin, &rmax);
/* These also handle constant (always same valued) subarrays */
if(lmin != lmax) {
/* Better than (a+b)/2 avg because of overflow possibilities */
uint32_t lavg = lmin + (lmax - lmin) / 2;
/* Recursion with inter "ensures" logn stack spacen need! */
meanqs_inter(array, low, pi - 1, lavg, state);
}
if(rmin != rmax) {
/* Better than (a+b)/2 avg because of overflow possibilities */
uint32_t ravg = rmin + (rmax - rmin) / 2;
/* Recursion with inter "ensures" logn stack spacen need! */
meanqs_inter(array, pi + 1, high, ravg, state);
}
}
}
// TODO: Idea: Quadratic time happens when you repeatedly pick pivots close to the maximum or minimum - so why not re-pick near extremals being picked which now can be cheked? I think we should have a treshold - maybe dynamically set by first minmax - and logarithmically divide the treshold by 2 in case its still too close the next randomization time. This handles smallrange badly though.
// TODO: Guess the pivot value by previous min-maxes and let us use it inn partitioning (which would lead to "closest to pivot value" being swapped to its right position to keep the left:pivot:right separation architecture. Maybe can be faster than rand, although our randoms are pretty darn fast as you can see.
// TODO: Hackzolt éjféli ötlet:
// - Particionálás min-max-avgleft-el
// - két irányba rekurzió, bal oldalinak avgleft átadása, másik sima zssort_rand
// - avgleft-es rekurzióból visszahívás ennek a jelenleginek a fő függvényére (ide-oda típusú rekurzió!)
// - esetleg ha megéri, akkor avgright kiszámítása is jó lehet: drágább műveletek, de jobb algoritmus... hmmm
#endif /* ZS_SORT_H */