基数排序——浮点数结构体进阶

基数排序——浮点数结构体进阶

前置

这个东西曾经在个人Luogu Blog写过,因此还没有学过基数排序请先使用这篇文章入门。php

上面讲到了一种对于整数划分红二进制进行基数排序的方法,而且用结构体指针转换为整型指针来进行强制类型转换把这个方法扩展到告终构体上。数组

可是当时这个方法存在很大的局限性,就是它必须依赖于整数,这也就意味着结构体大小最大为64bit,可是咱们考虑即便使用pimpl手法(也就是一个指针指向另一个结构体)的结构体,在64bit机子下的指针大小64bit,那么这样就没什么用了。优化

而后最近从新思考了一下指针在这种基数排序内的做用,而后稍微了解了一下浮点数的原理,因而就想到了一种对于浮点数进行基数排序的方法。编码

结构体

咱们注意到,咱们以前使用结构体指针转整数指针对结构体进行了排序,可是若是待排序位数大于64bit的话那么long long没法彻底表示这个结构体,例如两个long long须要进行双关键字排序那么就会咕咕。spa

因而咱们考虑上面的指针。咱们发现指针是能够移动的,也就是能够经过++ --来移动到想要的内存位置。那么咱们是否是能够经过用一个char指针先指向一位,而后逐位扫描呢?是能够的。由于每次基数排序的时候若是桶的大小是一个字节的话这样就能够了。设计

因而咱们考虑以下代码指针

 1 template <typename T>
 2 inline void Radix_Sort_Bit(register const int n, T *a, T *b){
 3     size_t size_of_type = sizeof(a);
 4     size_t num_of_buc = ((size_of_type >> 1) + 1) >> 1;
 5     unsigned r[num_of_buc][0x10000];
 6     memset(r, 0, sizeof(r));
 7     register int i, k;
 8     register unsigned short tmp_us;
 9     register T * j, *tar;
10     for(k = 0; k < num_of_buc; ++k){
11         for(j = a + 1, tar = a + 1 + n; j != tar; ++ j){
12             tmp_us = * (((unsigned short *)j) + k);
13             ++ r[k][tmp_us];
14         }
15     }
16     for(k = 0; k < num_of_buc; k++)
17         for(i = 1; i <= 0xffff; i++)
18             r[k][i] += r[k][i - 1];
19     for(k = 0; k < num_of_buc; k += 0x2){
20         i = k;
21         for(j = a + n; j != a; --j){
22             tmp_us = * (((unsigned short *)j) + i);
23             b[r[i][tmp_us]--] = *j;
24         }
25         i |= 1;
26         for(j = b + n; j != b; --j){
27             tmp_us = * (((unsigned short *)j) + i);
28             a[r[i][tmp_us]--] = *j;
29         }
30     }
31 }

而后上面代码适用于结构体大小不肯定的状况下,若是结构体大小肯定那么就能够手动进行下列优化code

1. 数组开外面或者栈内blog

2. 适当循环展开排序

3. 更改二进制拆分

同时这份代码适用于小端法机器(Link),若是是大端法机器的话注意k的循环顺序

浮点数

下面说的“实数”指的是数学意义上的,“浮点数”表示计算机储存的“实数”

好像基数排序在一开始就不方便作实数,一个指定的整数范围内的实数就是无限多的,而后浮点数后面又会有高精度位什么的会下降基数排序效率,并且十进制对浮点数的提取也比较困难,由于实际上0.1在计算机中没法精确表示。

可是IEEE 754给二进制浮点数一些很是优美的性质

二进制浮点数被设计成用$s * 2 ^ E * M$表示,其中s是符号位,即采用整数中原码的表示正负的方式,E是阶码,M是尾码

一个C++ 中的double被表示成二进制的方法以下:

sign(1bit)是符号位,exponent(11bit)是阶码位,fraction(52bit)是尾数位

By Codekaizen - Own work, CC BY-SA 4.0, Link

而后具体的编码方式不在此解释,能够查看上面IEEE 754连接

而后咱们要用的性质

若是有两个浮点数a, b,且他们不是NAN或者INFINITY,浮点数大小f(a), f(b),若是忽略符号位,那么剩下的位在解释为无符号整数编码后获得无符号数w(a), w(b),知足若w(a) <= w(b),则f(a) <= f(b)

因而咱们就能够获得一种排序思路:先带符号位排序,而后对负数部分特殊处理

同时咱们注意到float 32bit, double 64bit, long double 80bit(stored as 96bit(x86) or 128bit(x64)), __float128 128bit,这样的话若是用超过64位的浮点数好像会不便于排序

可是咱们能够像上面同样像结构体同样排序,而后最后处理一下符号位便可(直接使用stl的reverse)

1 inline void Radix_Sort_Double(register const int n, double *a, double *b){
2     Radix_Sort_64Bit(n, a, b);
3     reverse(a + 1, a + 1 + n);
4     reverse(upper_bound(a + 1, a + 1 + n, double(-0.0)), a + 1 + n);
5 }

最后一个有意思的事情是经过这种方法能够获得以下序列

-inf -inf -234.000000 -234.000000 -123.000000 -123.000000 -0.100000 -0.100000 -0.000000 -0.000000 nan nan 0.000000 0.000000 0.100000 0.100000 123.000000 123.000000 234.000000 234.000000 inf inf

-0.0 < nan < 0.0

很是优美,若是使用stl的sort的话对inf和nan的处理都不够好

相关文章
相关标签/搜索