基数排序对被排序的类型有很强的要求,如对整数的排序,就是在我们知道整数在内存中的表示方法的前提下,才得以实现。当然其对被排序类型要求严格,带来的回报就是时间复杂度达到了线性。下面是我实现的基数排序,代码包涵算法本身,测试方法,时间统计等逻辑,下面的代码可以在支持C++11的g++上直接编译通过,并运行测试。
基数排序的思路如下:
首先假设所有元素都是无符号正整数
确定基数大小,例如255,也就是每一轮比较8bit(0-255)
然后计算出需要做多少轮基数排序,每一轮基数排序做如下动作
1、统计基数范围内的每个数的个数
2、由1的统计信息,计算出每个数的起始位置
3、根据2的计算结果,将被排序元素拷贝到临时数组的相应位置。
如果是有符号数,则将整个排序集合划分为负数部分和非负数部分。
负数部分进行取反(满足正整数假设)、基数排序、反转、取反。
#include <iostream>
#include <algorithm>
#include <utility>
#include <cstdlib>
#include <ctime>
#include <random>
#include <limits>
#include <functional>
#include <type_traits>
#include <vector>
#include <chrono>
#include <cassert>
template<typename T,const unsigned int W = 8>
class radix_sort
{
public:
void operator()(T *a,const unsigned int sz) { return rsort(a,sz); }
private:
class radix_sort_impl
{
public:
void operator()(T *a,const unsigned int sz);
private:
static constexpr unsigned int passes = sizeof(T) * 8u / W;
static constexpr unsigned int buckets_size = (1u << W) + 1;
static constexpr unsigned int mask = (1u << W) - 1;
void sort(T *a,const unsigned int sz);
};
//if T is not integral type, produce compile error
typedef typename std::enable_if<std::is_integral<T>::value,radix_sort_impl>::type radix_sort_impl_t;
radix_sort_impl_t rsort;
};
template<typename T,const unsigned int W>
void radix_sort<T,W>::radix_sort_impl::sort(T *a,const unsigned int sz)
{
std::vector<T> buckets(buckets_size);
T *oa = a;
T *t = new T[sz];
for(int i = 0; i < passes; ++i)
{
for(auto &e : buckets)
e = 0;
//statistic count of every bucket
for(int j = 0;j < sz; ++j)
++buckets[((a[j] >> (i * W)) & mask) + 1];
//calculate every bucket start index
for(int j = 1; j < buckets_size; ++j)
buckets[j] += buckets[j - 1];
//copy to proper bucket
for(int j = 0;j < sz; ++j)
t[buckets[(a[j] >> (i * W)) & mask]++] = a[j];
std::swap(a,t);
}
if(oa != a)
{
std::swap(a,t);
for(int j = 0; j < sz; ++j)
a[j] = t[j];
}
delete [] t;
return;
}
template<typename T,const unsigned int W>
void radix_sort<T,W>::radix_sort_impl::operator()(T *a,const unsigned int sz)
{
//if T is signed, divide into negative part and positive part
if(std::is_signed<T>::value)
{
int i = 0;
int j = sz - 1;
while(true)
{
while(i < j && a[i] < 0) ++i;
while(i < j && a[j] >= 0) --j;
if(i < j)
std::swap(a[i],a[j]);
else
break;
}
//sort negative part
int negetive_size = a[i] < 0 ? i + 1 : i;
//remove minus
for(int k = 0; k < negetive_size; ++k)
a[k] = -a[k];
//sort
this->sort(a,negetive_size);
//reverse
std::reverse(a,a + negetive_size);
//add minus
for(int k = 0; k < negetive_size; ++k)
a[k] = -a[k];
//sort positive part
this->sort(a + negetive_size,sz - negetive_size);
}
else
{
this->sort(a,sz);
}
return;
}
//print number
int dump(int *a,int n)
{
for(int i = 0; i < n; ++i)
{
std::cout << a[i] << " ";
}
std::cout << std::endl;
return 0;
}
//generate random number
int gen(int *a,int n)
{
//srand(time(NULL));
std::default_random_engine dre(time(NULL));
std::uniform_int_distribution<int> uid(std::numeric_limits<int>::min(),std::numeric_limits<int>::max());
while(--n >= 0)
{
//a[n] = rand();
a[n] = uid(dre);
}
return 0;
}
//check sort result
bool chk(int *a,int n)
{
for(int i = 0;i < n - 1; ++i)
if(a[i] > a[i + 1])
return false;
return true;
}
int main(int argc,char *argv[])
{
if(argc != 2 && argc != 3)
{
return 0;
}
int loop = atoi(argv[1]);
int n = 10;
if(argc == 3)
n = atoi(argv[2]);
int *a = new int [n];
radix_sort<int> rs;
while(--loop >= 0)
{
gen(a,n);
//dump(a,n);
auto start = std::chrono::steady_clock::now();
rs(a,n);
auto end = std::chrono::steady_clock::now();
assert(true == chk(a,n));
//dump(a,n);
std::cout << "----------"<< std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count()<< "--------------" << std::endl;
}
delete [] a;
return 0;
}