详谈基数排序

目录

0. 基数排序由来

1. 基数排序图解

2. 基数排序代码

3. 基数排序测试

4. 小结

5. 参考资料

0. 基数排序由来

有一个学生记录数组,数组元素包含三个字段:系别,班号,班内编号。现在要对这个数组排序,使得系别小的在前,系别相同的班号小的在前,班号相同则班内编号较小的在前。这个排序与一般的排序要求(只对某个关键字段排序)不同的是:对多个关键字段排序。有2种方法可解决:一种是扫描一遍记录,将之按系别分组,再对组内记录按班号分组,然后对组内记录进行单关键字排序;另一种方法是,将这个数组按班内编号排序(班内编号在确定的范围内),然后再对整个数组按班号排序(班号在确定的范围内),最后对整个数组按系别排序(系别在确定的范围内)。第一种方法采取的策略是最高位优先排序。第二种方法采取分步骤按关键字类别排序的策略通常称为基数排序,可以看出基数排序的特点是各关键字段的分布是在确定范围(一般较小)内的。

对单关键字排序也可采用多关键字排序的思想:将单关键字分解成多个关键字段,在可控范围内,不用比较,直接排序。

计算机实现基数排序方式:分配和收集。按关键字段将记录重新分成若各组,再将各个组有序组合成新记录序列。

1. 基数排序图解

1.1 基数排序图示

1) 辅助队列实现基数排序

不足:需要额外利用队列来辅助排序,存在空间浪费。

2)链队列实现基数排序

 

3)基于桶有序的基数排序(也称计数排序,统计子关键字的出现频率)

考虑一种简单情况,如果你给一些unsigned char 排序,除了教科书上的很多方法外,还有一种简单的。可以这样考虑,试想排一系列的unsigned char, 值从0~ 255 , 放在 u_char data_array[ARRAY_SIZE] 中,分配一个数组 int membuffer[256]; 用于统计每个元素出现的次数,也就成为了数组的排序结果。

1 for(int i=0;i<256;i++) 
2   membuffer[i]=0; 
3 for(int i=0;i<ARRAY_SIZE;i++) 
4   membuffer[data_array[i]]++; 

1.2 基数排序分析

基数排序的优点就是:不用比较,每个关键字的范围确定且一般较小,排序时间复杂度低。但为什么基数排序的应用范围没有快速排序那样广呢?原因有以下几点:

1)基数排序一般需要额外的存储空间:顺序队列实现需要O(n)的元素空间,链队列实现需要O(n+2d)个指针空间,计数实现也需要O(n)的元素空间

2)基数排序的时间复杂度看似O(n),但它每次排序都是对整个数组的处理,相对快排来讲,cache局部性不好。

在我的印象中,基数排序一般出现在面试中,在实际生产环境中还没有发现采用基数排序。

2. 基数排序代码

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<string.h>
 4 
 5 // 基于无符号整数的基数排序
 6 void radix(int byte, unsigned int N, const unsigned int *source, unsigned int *dest) {
 7   unsigned int count[256];
 8   unsigned int index[256];
 9   int i = 0;
10 
11   memset(count, 0, sizeof (count));
12 
13   // 统计出现频率
14   for (i=0; i < N; ++i) {
15     ++count[((source[i])>>(byte*8))&0xff]; // 以一个字节为单位
16   }
17 
18   // 为dest下标初始化做准备
19   index[0] = 0;
20   for (i = 1; i < 256; ++i) {
21     index[i] = index[i-1] + count[i-1];
22   }
23 
24   // 数组重排列
25   for (i = 0; i < N; ++i) {
26     dest[index[((source[i])>>(byte*8))&0xff]++] = source[i];
27   }
28 }
29 
30 void radixsort (unsigned int *source, unsigned int *temp, unsigned int N) {
31   radix(0, N, source, temp);
32   radix(1, N, temp, source);
33   radix(2, N, source, temp);
34   radix(3, N, temp, source);
35 }
36 
37 
38 int main(int argc, char** argv) {
39   unsigned int a[5] = {300, 200, 2,20, 5};
40   unsigned int len = 5;
41   unsigned int b[5] = {0};
42   unsigned int i = 0;
43 
44   radixsort(a, b, len);
45   for (; i < 5; ++i) {
46     printf("%ld ", a[i]);
47   }
48 
49   return 0;
50 }

3. 基数排序测试

1)上节代码的运行结果如下:

2)网友的性能对比测试:

引用网络上的测试数据:

今实测性能在n =1024*1000的时候,VC 6 上 release 模式,排序时间:(tick)

n                           Radix Sort                  STL Sort
100*1024              20                              20
1000*1024            250                            320
4000*1024            991                            1513
10000*1024          2093                           3606

从以上数据可见Radix Sort 的确可能比STL Sort 快,而且,因为Radix Sort时间复杂度小,在更长的时间内表现更为充分。n 从100* 1024增长到10000*1024, 排序时间也从20 增长到2093, 可以看出的确是O(n)的。

4. 小结

基数排序采用多关键字的排序思想,由最低位到最高位逐步处理各关键字断序列,达到了O(n)的时间复杂度,但增加了空间复杂度。

基数排序一般适用于已知关键字的分布情况,且分布比较集中。计算机实现一般采用分配-收集策略或计数方式(统计各关键字的出现频率)。

5. 参考资料

Radix Sort 的介绍

原文地址:https://www.cnblogs.com/didiaoxiong/p/3259987.html