在学习了计数排序后,可以发现一个很严重的问题,如果数据很大呢,不如说每个元素小于2^64 - 1,不仅超时,而且正常数据下内存直接炸了。
(虽然可以直接用快排,但是为了讲解基数排序还是讲一下基数排序)
基数排序可以说成是改良版的桶排(有点类似,基数排序还是属于一种"分配式"排序),还是将一些数放入指定的桶中
比如一串数 12 33 43 54 65 39 45 72 89
首先按照个位分配到 0 - 10 这11个“桶”中
0 1 2 12 72 3 33 43 4 54 5 65 45 6 7 8 9 39 89
接着再串起来,得到了下面这样的数列
12 72 33 43 54 65 45 39 89
再按照十位进行分配桶
0 1 12 2 3 33 39 4 43 45 5 54 6 65 7 72 8 89 9
同上,再把这些桶串起来
12 33 39 43 45 54 65 72 89
这时候的数列已经是有序的了,不需要下一轮的分配了
这样不如说成从低位到高位,按位排序
可能可以想到,有没有可能出现从小到大排序出现这样的情况
46 42
这样是不会出现的,稍微想一下就可以明白
先按个位排序,46第一次排序完了一定是在42的后面
再按十位排序,在4这个桶内46一定是42后面,因为是按顺序放在后面
上面这种方法又叫做LSD(最低位优先)法,先给出LSD的实现(中间用到了
计数排序,和上面有一点不同,但是仍然不难理解)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 int* counter; 6 int* buf; 7 int maxbit(int* array, int len){ 8 int result = 1; 9 int p = 1; 10 for(int i = 0;i < len;i++){ 11 while(((p << 3) + (p << 1)) <= array[i]){ 12 p = (p << 3) + (p << 1); //p *= 10; 13 result++; 14 } 15 } 16 return result; 17 } 18 void radixSort(int* &array, int len){ 19 buf = new int[(const int)(len + 1)]; 20 counter = new int[11]; 21 int limit = maxbit(array, len); 22 int p = 1; 23 for(int i = 0;i < limit;i++){ 24 memset(counter, 0, sizeof(int) * 11); 25 for(int j = 0;j < len;j++) 26 counter[(array[j] / p) % 10]++; 27 for(int j = 1;j < 11;j++) 28 counter[j] += counter[j - 1]; 29 for(int j = len - 1;j >= 0;j--) 30 buf[--counter[(array[j] / p) % 10]] = array[j]; 31 swap(array, buf); 32 p = (p << 3) + (p << 1); 33 } 34 delete[] buf; 35 delete[] counter; 36 } 37 int n; 38 int *a; 39 int main(){ 40 cin>>n; 41 a = new int[(const int)n]; 42 for(int i = 0;i < n;i++) 43 cin>>a[i]; 44 radixSort(a, n); 45 for(int i = 0;i < n;i++) 46 cout<<a[i]<<" "; 47 return 0; 48 }
如果从高位开始按位排序又叫做MSD(最高位优先)法
如果您按照上面那种方法去做,很快就可以发现在对于这样一组数据是不行的
32 13 12
MSD的做法除了顺序外还有不同之处,如果一个桶中的元素不止一个,就对这个桶中
的元素进行下一位的排序
例如上面这组数据,首先第一轮分配
0 1 13 12 2 3 32
1这个桶内的元素不止一个,递归调用进行第二轮分配,分配个位
0 1 2 12 3 13
发现所有的桶中的元素都没有超过1个,返回(另外还要拷贝并覆盖原来那
如果像LSD那样用计数排序(省内存),貌似不太好处理,不过请仔细观察counter数组
在排序的过程中,counter对应位上值会减去一,排序完后这一位相当于前一位原来的值
如果counter[i] != counter[i + 1]说明i这个桶中有东西,计算有多少个东西就直接
counter[i + 1] - counter[i]就能算出来了,起始位置就是array[counter[i]],这样就解决
另外还要注意,如果是最低位就不用再递归调用了(如果忘记这一点可能不会超时,应该很快就
会整数被0除)
附上MSD的代码(也没改多少)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 int* buf; 6 int p; 7 int maxbit(int* array, int len){ 8 int result = 1; 9 p = 1; 10 for(int i = 0;i < len;i++){ 11 while(((p << 3) + (p << 1)) <= array[i]){ 12 p = (p << 3) + (p << 1); //p *= 10; 13 result++; 14 } 15 } 16 return result; 17 } 18 /** 19 * 基数排序MSD法 20 * @param array 数组的起始位置 21 * @param len 长度 22 * @param status 辅助作用 23 */ 24 void radixSort(int* array, int len, int status){ 25 buf = new int[(const int)(len + 1)]; 26 int* counter = new int[11]; //一定要定成局部变量 27 memset(counter, 0, sizeof(int) * 11); 28 for(int j = 0;j < len;j++) 29 counter[(array[j] / status) % 10]++; 30 for(int j = 1;j < 10;j++) 31 counter[j] += counter[j - 1]; 32 for(int j = len - 1;j >= 0;j--) 33 buf[--counter[(array[j] / status) % 10]] = array[j]; 34 for(int i = 0;i < len;i++) 35 array[i] = buf[i]; 36 delete[] buf; 37 for(int i = 0;i < 9;i++){ /* 这里开始 */ 38 int l = counter[i]; 39 int r = counter[i + 1] - 1; 40 if(l < r && status > 1){ 41 radixSort(array + counter[i], r - l + 1, status / 10); 42 } 43 } /* 这里结束 */ 44 delete[] counter; 45 } 46 int n; 47 int *a; 48 int main(){ 49 cin>>n; 50 a = new int[(const int)n]; 51 for(int i = 0;i < n;i++) 52 cin>>a[i]; 53 maxbit(a, n); 54 radixSort(a, n, p); 55 for(int i = 0;i < n;i++) 56 cout<<a[i]<<" "; 57 return 0; 58 }
[后话]
看完之后,仔细思考,应该可以发现一个很严重的问题——不能给负数排序
那怎么办呢?
那我就说说的几种方法(当然差值不能太大)
1)全部加上最小的负数的相反数,输出时候减去这么多
2)将非负整数用一个数组存储,将负数用另外一个数组存储,负数并且全部取反(是数学上的-)
先将前者排序,再将后者用同一个函数排序,输出的时候先输出后者,从高下标往低下标输出,
并把负号添上,再输出前者,从低下标到高下标
基数排序给整数排序的时候并不是用得特别多,更多的时候是用于比较复杂度不是O(1)(多关键字)的时候排序,比如说年月日等等,
后缀数组的构造(当然要用倍增)用基数排序也会比用快排快很多