2010-04-21 67 views
7

我有一个浮点数类型的字段的数据结构。这些结构的集合需要按浮点值进行排序。有没有这样的基数排序实现。是否有一个很好的基数实现浮点数在C#

如果没有,是否有快速访问指数,符号和尾数的方法。 因为如果你首先在尾数,指数和指数上对浮点数进行排序。你在O(n)中排序浮点数。

+0

Isnt radixsort在概念上被认为是整数,或者至少是十进制数中的任何数字?请记住:浮动内部存储在双重系统中。 – 2010-04-21 17:17:36

+2

的确如此,但正如我所描述的那样,您可以做到这一点。你首先在尾数上进行排序(将尾数看作一个整数,而不使用符号)。之后,你将它们按指数排序(也是一个有符号的整数)。您通过标记(布尔值)对它们进行排序。通过运行三次基数排序算法,您可以对浮点数进行排序。 – 2010-04-21 17:20:11

+0

我明白你的观点。然而,O(n)排序算法可能比O(nlogn)标准排序慢,在大多数情况下,如果n从不执行某个均衡点。 – 2010-04-21 17:24:05

回答

17

更新:

我是这个话题很感兴趣,所以我坐下来实现它(使用this very fast and memory conservative implementation)。我还读了this one(谢谢celion),发现你甚至不需要将浮点数分成尾数和指数来对它进行排序。你只需要一点一点地进行比特并执行一个int类型。你只需要关心负值,在算法结束时必须反面放在正值的前面(我用一次最后一次迭代算法来节省一些CPU时间)。

所以,我的继承人浮动基数排序:

public static float[] RadixSort(this float[] array) 
{ 
    // temporary array and the array of converted floats to ints 
    int[] t = new int[array.Length]; 
    int[] a = new int[array.Length]; 
    for (int i = 0; i < array.Length; i++) 
     a[i] = BitConverter.ToInt32(BitConverter.GetBytes(array[i]), 0); 

    // set the group length to 1, 2, 4, 8 or 16 
    // and see which one is quicker 
    int groupLength = 4; 
    int bitLength = 32; 

    // counting and prefix arrays 
    // (dimension is 2^r, the number of possible values of a r-bit number) 
    int[] count = new int[1 << groupLength]; 
    int[] pref = new int[1 << groupLength]; 
    int groups = bitLength/groupLength; 
    int mask = (1 << groupLength) - 1; 
    int negatives = 0, positives = 0; 

    for (int c = 0, shift = 0; c < groups; c++, shift += groupLength) 
    { 
     // reset count array 
     for (int j = 0; j < count.Length; j++) 
      count[j] = 0; 

     // counting elements of the c-th group 
     for (int i = 0; i < a.Length; i++) 
     { 
      count[(a[i] >> shift) & mask]++; 

      // additionally count all negative 
      // values in first round 
      if (c == 0 && a[i] < 0) 
       negatives++; 
     } 
     if (c == 0) positives = a.Length - negatives; 

     // calculating prefixes 
     pref[0] = 0; 
     for (int i = 1; i < count.Length; i++) 
      pref[i] = pref[i - 1] + count[i - 1]; 

     // from a[] to t[] elements ordered by c-th group 
     for (int i = 0; i < a.Length; i++){ 
      // Get the right index to sort the number in 
      int index = pref[(a[i] >> shift) & mask]++; 

      if (c == groups - 1) 
      { 
       // We're in the last (most significant) group, if the 
       // number is negative, order them inversely in front 
       // of the array, pushing positive ones back. 
       if (a[i] < 0) 
        index = positives - (index - negatives) - 1; 
       else 
        index += negatives; 
      } 
      t[index] = a[i]; 
     } 

     // a[]=t[] and start again until the last group 
     t.CopyTo(a, 0); 
    } 

    // Convert back the ints to the float array 
    float[] ret = new float[a.Length]; 
    for (int i = 0; i < a.Length; i++) 
     ret[i] = BitConverter.ToSingle(BitConverter.GetBytes(a[i]), 0); 

    return ret; 
} 

这是因为在功能,其中花车复制到按位的开始和结束的阵列复制比int基数排序稍微慢一些, ints和后面。然而,整个功能仍然是O(n)。在任何情况下,比您提议的排序连续3次快得多。我没有看到太多的优化空间,但如果有人愿意:随时告诉我。

排序在最后降改变这一行:

ret[i] = BitConverter.ToSingle(BitConverter.GetBytes(a[i]), 0); 

这样:

ret[a.Length - i - 1] = BitConverter.ToSingle(BitConverter.GetBytes(a[i]), 0); 

测量:

我设置了一些简短的测试,包含所有特殊漂浮物(NaN,+/- Inf,最小/最大值,0)和随机数。它排序完全相同的顺序为LINQ的或Array.Sort各种彩车:

NaN -> -Inf -> Min -> Negative Nums -> 0 -> Positive Nums -> Max -> +Inf 

所以我跑测试与一个巨大的10M数字数组:

float[] test = new float[10000000]; 
Random rnd = new Random(); 
for (int i = 0; i < test.Length; i++) 
{ 
    byte[] buffer = new byte[4]; 
    rnd.NextBytes(buffer); 
    float rndfloat = BitConverter.ToSingle(buffer, 0); 
    switch(i){ 
     case 0: { test[i] = float.MaxValue; break; } 
     case 1: { test[i] = float.MinValue; break; } 
     case 2: { test[i] = float.NaN; break; } 
     case 3: { test[i] = float.NegativeInfinity; break; } 
     case 4: { test[i] = float.PositiveInfinity; break; } 
     case 5: { test[i] = 0f; break; } 
     default: { test[i] = test[i] = rndfloat; break; } 
    } 
} 

,并停止的不同的排序算法的时间:

Stopwatch sw = new Stopwatch(); 
sw.Start(); 

float[] sorted1 = test.RadixSort(); 

sw.Stop(); 
Console.WriteLine(string.Format("RadixSort: {0}", sw.Elapsed)); 
sw.Reset(); 
sw.Start(); 

float[] sorted2 = test.OrderBy(x => x).ToArray(); 

sw.Stop(); 
Console.WriteLine(string.Format("Linq OrderBy: {0}", sw.Elapsed)); 
sw.Reset(); 
sw.Start(); 

Array.Sort(test); 
float[] sorted3 = test; 

sw.Stop(); 
Console.WriteLine(string.Format("Array.Sort: {0}", sw.Elapsed)); 

输出功率为(更新:现在发行版本跑了,无法调试):

RadixSort: 00:00:03.9902332 
Linq OrderBy: 00:00:17.4983272 
Array.Sort: 00:00:03.1536785 

大约比Linq快4倍以上。这并不坏。但仍然没有像Array.Sort那么快,但也没那么糟。但是我对这一点感到非常惊讶:我预计它会比Linq在非常小的阵列上慢一点。但后来我跑了仅20元一个测试:

RadixSort: 00:00:00.0012944 
Linq OrderBy: 00:00:00.0072271 
Array.Sort: 00:00:00.0002979 

,甚至这一次我的基数排序比LINQ的更快,但比数组排序慢方式。 :)

更新2:

我做了一些测试,发现了一些有趣的事情:再组长度的常量意味着更少的迭代和更多的内存使用情况。如果使用16位组的长度(只有2次迭代),那么在对小阵列进行排序时存在巨大的内存开销,但如果涉及大于大约100k个元素的数组(即使不是很多),则可以击败Array.Sort。这些图表轴均取对数:

comparison chart http://daubmeier.de/philip/stackoverflow/radixsort_vs_arraysort.png

+2

顺便说一句,该算法同样适用于'double'数组,只需用'double'替换'float',用'long'替换'int' ,'ToInt32'由'ToInt64','.ToSingle'由'.ToDouble'和'int bitLength = 32;'改为64. – 2010-04-21 23:10:39

+0

干得好!我没想到有人会实施这个问题。非常好的代码和分析。 :d – 2010-04-22 00:17:03

0

我认为你最好的选择,如果值不是太接近并且有合理的精度要求,你可以使用小数点前后的实际浮点数进行排序。

例如,您可以使用前4位小数(无论它们是否为0)来进行排序。

0

您可以使用unsafe块将memcpy或别名float *设置为uint *以提取这些位。

相关问题