浮点数-基数排序
这段时间在研究海量浮点数外部排序时,研究了内部排序的一些算法,一直也没往基数排序上想。这两天恰好看到了RadixSortRevisited这篇文章,感触很深,便想尝试复现下浮点数基数排序算法。
传统的基数排序的原理就是将整数按位进行切分,然后按每个位数分别进行比较。该方法网上资料很多,这里就不做一一赘述。
根据IEEE754标准,double类型的数据在存储中会被划分为1、11、52三个段。1代表数符,11代表阶码,52代表尾数。具体的标准可查阅相关资料。
算法的第一步就是要理解将double类型的数据强制转化成无符号长整型进行基数排序。由于double类型的数据占64位,所以我们可以考虑将这64位进行划分,比如划分成4个部分,每个部分16位,即桶的大小为65536。可类比十进制基数排序,十进制基数排序每一位桶的大小为10,而将浮点数如此划分之后,每个部分桶的大小为65536。
算法的第二步就是理解将这些浮点数强制转化后进行基数排序,最后得到的结果如何。可以发现,正数的数符位总为0,负数的数符位总为1,所以最后排序后的结果:正数小-正数大-负数大-负数小。
算法的第三步就是要调整排序后的顺序,即将排序后的结果更正为负数小-负数大-正数小-正数大。这一步就需要我们在之前就统计好负数或者正数的个数,在此统计负数个数会更加方便。
粗略描述了下算法的步骤后,代码如下:
// 浮点数在计算机中的存储方式:https://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html
// https://www.boatsky.com/blog/26
// http://www.codercorner.com/RadixSortRevisited.htm
// 2.5亿个数尝试划分成8份、4份,划分成4份的效果最好,但两种都比C++内置sort好
void in_sort(double *buffer, long long n, int sort_seq_num) {
// 一共64位,若按每两个字节(16位)对其进行排序,类比十进制的基数排序,所以分成了4份,此时
// sort_seq_num = 4
// 每16位有一个bucket数组,数组大小为65536,bucket数组进行计数
int bit_num = 64 / sort_seq_num;
int *bucket = new int[1 << (bit_num)];
// 临时存储
auto *temp = (double *)malloc((n + 100) * sizeof(double));
long long negative_num = 0; //记录负数的个数
// 将数转化成无符号长整型对其进行基数排序
for (int i = 0; i < sort_seq_num; i++) {
for (int j = 0; j < (1 << bit_num); ++j) {
bucket[j] = 0;
}
for (long long j = 0; j < n; j++) {
if (i == 0 && buffer[j] < 0) {
negative_num++;
}
int idx;
unsigned long long val;
val = reinterpret_cast<unsigned long long &>(buffer[j]);
idx = val >> i * bit_num & ((1 << bit_num) - 1); //每16位提取一次
bucket[idx]++;
}
//求出基数桶的边界索引
for (int j = 1; j < (1 << bit_num); j++) {
bucket[j] = bucket[j - 1] + bucket[j];
}
//从右向左扫描,保证排序稳定性,记录进temp中
for (long long j = n - 1; j >= 0; j--) {
int idx;
unsigned long long val;
val = reinterpret_cast<unsigned long long &>(buffer[j]);
idx = val >> i * bit_num & ((1 << bit_num) - 1);
temp[--bucket[idx]] = buffer[j];
}
for (long long j = 0; j < n; j++) {
buffer[j] = temp[j];
}
}
// 处理负数
for (long long i = 0; i < n; i++) {
if (temp[i] < 0.0) {
buffer[n - 1 - i] = temp[i];
} else {
buffer[negative_num + i] = temp[i];
}
}
free(temp);
delete[] bucket;
}
其中sort_seq_num代表的就是想把64位长整型划分为多少个部分,即算法的第二步需要比较多少次。