106 lines
4.1 KiB
C
106 lines
4.1 KiB
C
#include <c_InPlaceMSDRadixSort.h>
|
|
#include <c_Memory.h>
|
|
|
|
/**
|
|
* Inline helper to safely extract a character at string offset d.
|
|
* Automatically maps a string's null terminator to a sentinel value of -1.
|
|
*/
|
|
C_STATIC_FORCE_INLINE
|
|
int c_InPlaceMSDRadixSort_CharAt(const char* str, c_size_t d) {
|
|
if (str == NULL) return -1;
|
|
c_size_t i = 0;
|
|
while (i < d && str[i] != '\0') {
|
|
i++;
|
|
}
|
|
if (str[i] == '\0' || i < d) return -1;
|
|
return (unsigned char)str[i];
|
|
}
|
|
|
|
/**
|
|
* Core Private Recursive Sub-partition In-place Sorting Subroutine.
|
|
* Employs a localized head/tail lookup permutation ring to operate directly within array slices.
|
|
*/
|
|
static void c_InPlaceMSDRadixSort_Recursive(char** arr, long long lo, long long hi, c_size_t d,
|
|
c_size_t R, c_size_t* count_buf, long long* heads, long long* tails) {
|
|
if (hi <= lo) return;
|
|
|
|
// Elements are shifted forward by +2 slots to absorb the -1 string end sentinel gracefully
|
|
c_size_t total_buckets = R + 2;
|
|
memset(count_buf, 0, total_buckets * sizeof(c_size_t));
|
|
|
|
// Pass A: Compute frequency counts for the current digit slice
|
|
for (long long i = lo; i <= hi; i++) {
|
|
int c = c_InPlaceMSDRadixSort_CharAt(arr[i], d);
|
|
count_buf[c + 2]++;
|
|
}
|
|
|
|
// Pass B: Transform frequencies into absolute head and tail cursor index maps
|
|
heads[0] = lo;
|
|
tails[0] = lo + (long long)count_buf[0];
|
|
for (c_size_t r = 1; r < total_buckets; r++) {
|
|
heads[r] = tails[r - 1];
|
|
tails[r] = heads[r] + (long long)count_buf[r];
|
|
}
|
|
|
|
// Pass C: Cyclic Permutation Swap Element Loop (In-Place Distribution)
|
|
for (c_size_t r = 0; r < total_buckets; r++) {
|
|
while (heads[r] < tails[r]) {
|
|
long long curr_idx = heads[r];
|
|
int c = c_InPlaceMSDRadixSort_CharAt(arr[curr_idx], d);
|
|
c_size_t bucket = (c_size_t)(c + 2);
|
|
|
|
if (bucket == r) {
|
|
heads[r]++; // Element is already in its correct bucket, step forward
|
|
} else {
|
|
// Evict the element to its correct destination bucket via data swap
|
|
long long dest_idx = heads[bucket];
|
|
char* temp = arr[curr_idx];
|
|
arr[curr_idx] = arr[dest_idx];
|
|
arr[dest_idx] = temp;
|
|
|
|
heads[bucket]++; // Increment the destination bucket's cursor
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass D: Recursively process sub-arrays for each character bucket
|
|
// Shorter strings that terminated (sentinel character index 0) do not need deeper processing
|
|
long long current_lo = lo + (long long)count_buf[0];
|
|
for (c_size_t r = 1; r < total_buckets; r++) {
|
|
long long current_hi = current_lo + (long long)count_buf[r] - 1;
|
|
|
|
if (current_hi > current_lo) {
|
|
c_InPlaceMSDRadixSort_Recursive(arr, current_lo, current_hi, d + 1, R, count_buf, heads, tails);
|
|
}
|
|
current_lo = current_hi + 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts an array of variable-length strings completely in-place.
|
|
* Space Complexity: O(1) Auxiliary Heap memory footprint (Excluding recursive tracking arrays bound to R)
|
|
*/
|
|
c_err_t c_InPlaceMSDRadixSort_Sort(const c_InPlaceMSDRadixSort_t* sort, char** arr, c_size_t n) {
|
|
if (sort == NULL || arr == NULL || sort->R == 0) return C_ERR_PARAM;
|
|
if (n <= 1) return C_ERR_OK;
|
|
|
|
// Radix-bound tracking buffers are allocated once upfront to eliminate heap overhead in hot loops
|
|
c_size_t total_buckets = sort->R + 2;
|
|
c_size_t* count_buf = (c_size_t*)C_ALLOC(total_buckets * sizeof(c_size_t));
|
|
long long* heads = (long long*)C_ALLOC(total_buckets * sizeof(long long));
|
|
long long* tails = (long long*)C_ALLOC(total_buckets * sizeof(long long));
|
|
|
|
if (count_buf == NULL || heads == NULL || tails == NULL) {
|
|
C_FREE(count_buf); C_FREE(heads); C_FREE(tails);
|
|
return C_ERR_NOMEM;
|
|
}
|
|
|
|
// Launch the in-place cyclic permutation partition tree
|
|
c_InPlaceMSDRadixSort_Recursive(arr, 0, (long long)n - 1, 0, sort->R, count_buf, heads, tails);
|
|
|
|
C_FREE(count_buf);
|
|
C_FREE(heads);
|
|
C_FREE(tails);
|
|
return C_ERR_OK;
|
|
}
|