基數排序——浮點數結構體進階

基數排序——浮點數結構體進階

前置

這個東西曾經在個人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的處理都不夠好

相關文章
相關標籤/搜索