From 8b57a795670c3c57233258b4f70c8ee85eca6738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Sat, 4 Dec 2021 15:14:31 +0000 Subject: [PATCH] Change qsort(3) and qsort_r(3) to use heapsort. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Heapsort has O(n·log(n)) worst case runtime and O(1) space usage, which is better than current implementation's best case, while still keeping the code simple. Co-authored-by: Jonas 'Sortie' Termansen --- libc/stdlib/qsort_r.c | 150 +++++++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/libc/stdlib/qsort_r.c b/libc/stdlib/qsort_r.c index 0e72b49a..8f434c9e 100644 --- a/libc/stdlib/qsort_r.c +++ b/libc/stdlib/qsort_r.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2012, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2012, 2014, 2021 Jonas 'Sortie' Termansen. + * Copyright (c) 2021 Juhani 'nortti' Krekelä. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,11 +15,11 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * stdlib/qsort_r.c - * Sort an array. + * Sort an array. Implemented using heapsort, which is not a stable sort. */ +#include #include -#include static void memswap(unsigned char* a, unsigned char* b, size_t size) { @@ -31,69 +32,114 @@ static void memswap(unsigned char* a, unsigned char* b, size_t size) } } -static -unsigned char* array_index(unsigned char* base, - size_t element_size, - size_t index) +static unsigned char* array_index(unsigned char* base, + size_t element_size, + size_t index) { return base + element_size * index; } -static -size_t partition(unsigned char* base, - size_t element_size, - size_t num_elements, - size_t pivot_index, - int (*compare)(const void*, const void*, void*), - void* arg) -{ - if ( pivot_index != num_elements - 1 ) - { - unsigned char* pivot = array_index(base, element_size, pivot_index); - unsigned char* other = array_index(base, element_size, num_elements - 1); - memswap(pivot, other, element_size); - pivot_index = num_elements - 1; - } - - size_t store_index = 0; - for ( size_t i = 0; i < num_elements - 1; i++ ) - { - unsigned char* pivot = array_index(base, element_size, pivot_index); - unsigned char* value = array_index(base, element_size, i); - if ( compare(value, pivot, arg) <= 0 ) - { - unsigned char* other = array_index(base, element_size, store_index); - if ( value != other ) - memswap(value, other, element_size); - store_index++; - } - } - - unsigned char* pivot = array_index(base, element_size, pivot_index); - unsigned char* value = array_index(base, element_size, store_index); - memswap(pivot, value, element_size); - - return store_index; -} - void qsort_r(void* base_ptr, size_t num_elements, size_t element_size, int (*compare)(const void*, const void*, void*), void* arg) { - unsigned char* base = (unsigned char*) base_ptr; + unsigned char* base = base_ptr; if ( !element_size || num_elements < 2 ) return; - size_t pivot_index = num_elements / 2; - pivot_index = partition(base, element_size, num_elements, pivot_index, compare, arg); + // Incrementally left-to-right transform the array into a max-heap, where + // each element has up to two children that aren't bigger than the element. + for ( size_t i = 0; i < num_elements; i++ ) + { + // Grow the heap by inserting another element into it (implicit, done by + // moving the boundary between the heap and the yet-to-be-processed + // array elements) and swapping it up the parent chain as long as it's + // bigger than its parent. + size_t element = i; + while ( element > 0 ) + { + unsigned char* ptr = array_index(base, element_size, element); + size_t parent = (element - 1) / 2; + unsigned char* parent_ptr = array_index(base, element_size, parent); + if ( compare(parent_ptr, ptr, arg) < 0 ) + { + memswap(parent_ptr, ptr, element_size); + element = parent; + } + else + break; + } + } - if ( 2 <= pivot_index ) - qsort_r(base, pivot_index, element_size, compare, arg); + // The array is split into two sections, first the max-heap and then the + // part of the array that has been sorted. + // Sorting progresses right-to-left, taking the biggest value of the heap + // (at its root), swapping it with the last element of the heap, and + // adjusting the boundary between the two sections such that the old biggest + // value is now part of the sorted section. + // After this, the max-heap property is restored by swapping the old last + // element down as long as it's smaller than one of its children. + for ( size_t size = num_elements; --size; ) + { + memswap(array_index(base, element_size, size), base, element_size); - if ( 2 <= num_elements - (pivot_index + 1) ) - qsort_r(array_index(base, element_size, pivot_index + 1), - num_elements - (pivot_index + 1), element_size, compare, arg); + size_t first_without_left = size / 2; + size_t first_without_right = (size - 1) / 2; + + size_t element = 0; + while ( element < size ) + { + unsigned char* ptr = array_index(base, element_size, element); + + size_t left = 2 * element + 1; + unsigned char* left_ptr = NULL; + bool left_bigger = false; + if ( element < first_without_left ) + { + left_ptr = array_index(base, element_size, left); + left_bigger = compare(ptr, left_ptr, arg) < 0; + } + + size_t right = 2 * element + 2; + unsigned char* right_ptr = NULL; + bool right_bigger = false; + if ( element < first_without_right ) + { + right_ptr = array_index(base, element_size, right); + right_bigger = compare(ptr, right_ptr, arg) < 0; + } + + if ( left_bigger && right_bigger ) + { + // If both the left and right child are bigger than the element, + // then swap the element with whichever of the left and right + // child is bigger. + if ( compare(left_ptr, right_ptr, arg) < 0 ) + { + memswap(ptr, right_ptr, element_size); + element = right; + } + else + { + memswap(ptr, left_ptr, element_size); + element = left; + } + } + else if ( left_bigger ) + { + memswap(ptr, left_ptr, element_size); + element = left; + } + else if ( right_bigger ) + { + memswap(ptr, right_ptr, element_size); + element = right; + } + else + break; + } + } }