#pragma once #include #include #include #include #include #include /** Поразрядная сортировка, обладает следующей функциональностью: * Может сортировать unsigned, signed числа, а также float-ы. * Может сортировать массив элементов фиксированной длины, которые содержат что-то ещё кроме ключа. * Настраиваемый размер разряда. * * LSB, stable. * NOTE Для некоторых приложений имеет смысл добавить MSB-radix-sort, * а также алгоритмы radix-select, radix-partial-sort, radix-get-permutation на его основе. */ /** Используется в качестве параметра шаблона. См. ниже. */ struct RadixSortMallocAllocator { void * allocate(size_t size) { return malloc(size); } void deallocate(void * ptr, size_t size) { return free(ptr); } }; /** Преобразование, которое переводит битовое представление ключа в такое целое беззнаковое число, * что отношение порядка над ключами будет соответствовать отношению порядка над полученными беззнаковыми числами. * Для float-ов это преобразование делает следующее: * если выставлен знаковый бит, то переворачивает все остальные биты. */ template struct RadixSortFloatTransform { /// Стоит ли записывать результат в память, или лучше делать его каждый раз заново? static constexpr bool transform_is_simple = false; static KeyBits forward(KeyBits x) { return x ^ (-((x >> (sizeof(KeyBits) * 8 - 1) | (KeyBits(1) << (sizeof(KeyBits) * 8 - 1))))); } static KeyBits backward(KeyBits x) { return x ^ (((x >> (sizeof(KeyBits) * 8 - 1)) - 1) | (KeyBits(1) << (sizeof(KeyBits) * 8 - 1))); } }; template struct RadixSortFloatTraits { using Element = Float; /// Тип элемента. Это может быть структура с ключём и ещё каким-то payload-ом. Либо просто ключ. using Key = Float; /// Ключ, по которому нужно сортировать. using CountType = uint32_t; /// Тип для подсчёта гистограмм. В случае заведомо маленького количества элементов, может быть меньше чем size_t. /// Тип, в который переводится ключ, чтобы делать битовые операции. Это UInt такого же размера, как ключ. using KeyBits = typename std::conditional::type; static constexpr size_t PART_SIZE_BITS = 8; /// Какими кусочками ключа в количестве бит делать один проход - перестановку массива. /// Преобразования ключа в KeyBits такое, что отношение порядка над ключём соответствует отношению порядка над KeyBits. using Transform = RadixSortFloatTransform; /// Объект с функциями allocate и deallocate. /// Может быть использован, например, чтобы выделить память для временного массива на стеке. /// Для этого сам аллокатор создаётся на стеке. using Allocator = RadixSortMallocAllocator; /// Функция получения ключа из элемента массива. static Key & extractKey(Element & elem) { return elem; } }; template struct RadixSortIdentityTransform { static constexpr bool transform_is_simple = true; static KeyBits forward(KeyBits x) { return x; } static KeyBits backward(KeyBits x) { return x; } }; template struct RadixSortSignedTransform { static constexpr bool transform_is_simple = true; static KeyBits forward(KeyBits x) { return x ^ (KeyBits(1) << (sizeof(KeyBits) * 8 - 1)); } static KeyBits backward(KeyBits x) { return x ^ (KeyBits(1) << (sizeof(KeyBits) * 8 - 1)); } }; template struct RadixSortUIntTraits { using Element = UInt; using Key = UInt; using CountType = uint32_t; using KeyBits = UInt; static constexpr size_t PART_SIZE_BITS = 8; using Transform = RadixSortIdentityTransform; using Allocator = RadixSortMallocAllocator; /// Функция получения ключа из элемента массива. static Key & extractKey(Element & elem) { return elem; } }; template struct RadixSortIntTraits { using Element = Int; using Key = Int; using CountType = uint32_t; using KeyBits = typename std::make_unsigned::type; static constexpr size_t PART_SIZE_BITS = 8; using Transform = RadixSortSignedTransform; using Allocator = RadixSortMallocAllocator; /// Функция получения ключа из элемента массива. static Key & extractKey(Element & elem) { return elem; } }; template struct RadixSort { private: using Element = typename Traits::Element; using Key = typename Traits::Key; using CountType = typename Traits::CountType; using KeyBits = typename Traits::KeyBits; static constexpr size_t HISTOGRAM_SIZE = 1 << Traits::PART_SIZE_BITS; static constexpr size_t PART_BITMASK = HISTOGRAM_SIZE - 1; static constexpr size_t KEY_BITS = sizeof(Key) * 8; static constexpr size_t NUM_PASSES = (KEY_BITS + (Traits::PART_SIZE_BITS - 1)) / Traits::PART_SIZE_BITS; static ALWAYS_INLINE KeyBits getPart(size_t N, KeyBits x) { if (Traits::Transform::transform_is_simple) x = Traits::Transform::forward(x); return (x >> (N * Traits::PART_SIZE_BITS)) & PART_BITMASK; } static KeyBits keyToBits(Key x) { return ext::bit_cast(x); } static Key bitsToKey(KeyBits x) { return ext::bit_cast(x); } public: static void execute(Element * arr, size_t size) { /// Если массив имеет размер меньше 256, то лучше использовать другой алгоритм. /// Здесь есть циклы по NUM_PASSES. Очень важно, что они разворачиваются в compile-time. /// Для каждого из NUM_PASSES кусков бит ключа, считаем, сколько раз каждое значение этого куска встретилось. CountType histograms[HISTOGRAM_SIZE * NUM_PASSES] = {0}; typename Traits::Allocator allocator; /// Будем делать несколько проходов по массиву. На каждом проходе, данные перекладываются в другой массив. Выделим этот временный массив. Element * swap_buffer = reinterpret_cast(allocator.allocate(size * sizeof(Element))); /// Трансформируем массив и вычисляем гистограмму. for (size_t i = 0; i < size; ++i) { if (!Traits::Transform::transform_is_simple) Traits::extractKey(arr[i]) = bitsToKey(Traits::Transform::forward(keyToBits(Traits::extractKey(arr[i])))); for (size_t j = 0; j < NUM_PASSES; ++j) ++histograms[j * HISTOGRAM_SIZE + getPart(j, keyToBits(Traits::extractKey(arr[i])))]; } { /// Заменяем гистограммы на суммы с накоплением: значение в позиции i равно сумме в предыдущих позициях минус один. size_t sums[NUM_PASSES] = {0}; for (size_t i = 0; i < HISTOGRAM_SIZE; ++i) { for (size_t j = 0; j < NUM_PASSES; ++j) { size_t tmp = histograms[j * HISTOGRAM_SIZE + i] + sums[j]; histograms[j * HISTOGRAM_SIZE + i] = sums[j] - 1; sums[j] = tmp; } } } /// Перекладываем элементы в порядке начиная от младшего куска бит, и далее делаем несколько проходов по количеству кусков. for (size_t j = 0; j < NUM_PASSES; ++j) { Element * writer = j % 2 ? arr : swap_buffer; Element * reader = j % 2 ? swap_buffer : arr; for (size_t i = 0; i < size; ++i) { size_t pos = getPart(j, keyToBits(Traits::extractKey(reader[i]))); /// Размещаем элемент на следующей свободной позиции. auto & dest = writer[++histograms[j * HISTOGRAM_SIZE + pos]]; dest = reader[i]; /// На последнем перекладывании, делаем обратную трансформацию. if (!Traits::Transform::transform_is_simple && j == NUM_PASSES - 1) Traits::extractKey(dest) = bitsToKey(Traits::Transform::backward(keyToBits(Traits::extractKey(reader[i])))); } } /// Если число проходов нечётное, то результирующий массив находится во временном буфере. Скопируем его на место исходного массива. if (NUM_PASSES % 2) memcpy(arr, swap_buffer, size * sizeof(Element)); allocator.deallocate(swap_buffer, size * sizeof(Element)); } }; template typename std::enable_if::value && std::is_integral::value, void>::type radixSort(T * arr, size_t size) { return RadixSort>::execute(arr, size); } template typename std::enable_if::value && std::is_integral::value, void>::type radixSort(T * arr, size_t size) { return RadixSort>::execute(arr, size); } template typename std::enable_if::value, void>::type radixSort(T * arr, size_t size) { return RadixSort>::execute(arr, size); }