基数排序
前面介绍的计数排序是一种线性时间(即时间复杂度为 O ( n ) O(n) O(n))的排序算法,这一篇介绍另外一种也是线性时间的排序方法:基数排序。
原理
基数排序最早是用于打孔卡片制表机上的算法,它可以给打孔的卡片进行排序。我们把打孔改为用数字表示,则它的基本思想是将整数按位数依次排序。步骤如下:
- 统一数据长度为相同的位数,数位较短的数前面补零。
- 按照个位数进行排序。
- 按照十位数进行排序。
- 按照百位数进行排序。
- 依次类推,一直到按照最高位进行排序。然后就结束了。
其中对每个位数进行排序时需要用稳定的排序算法。如果这个稳定的排序算法可以在线性时间完成,则基数排序也可以在线性时间完成。典型的,我们可以用前面介绍过的计数排序。
实现
按照以上原理我们来用代码实现。
下面就是用C语言实现的代码。
- 要排序的数组a有n个元素。分两个函数来实现。
- radix_sort 中先找到a中元素的最大值,再按给定基数radix计算最大位数,然后从低位到最高位依次调用 count_sort_radix 进行排序。注意基数radix的值是可以调整的,其大小会影响排序效率。
- count_sort_radix 是由前面介绍过的计数排序修改而来,按第d位进行计数排序,注意a中所有元素的每一位数的值都在[0,k]范围内。
/* a中所有元素的每一位数的值都在[0,k]范围内。 */
/* d表示按第几位进行排序,0表示按个位排序, */
/* 1表示按十位,2表示按百位,以此类推。 */
void count_sort_radix(int a[], int n, int k, int d)
{
int *b, *c;
int i;
int num = 1; //便于提取出a中的某一位数的值
b = (int*)malloc(sizeof(int)*n);
if (b==NULL) return;
c = (int*)malloc(sizeof(int)*(k+1));
if (c==NULL) {
free(b);
return;
}
memset(c, 0, sizeof(int)*(k+1));
while (d-->0) num *= k+1;
for (i=0; i<n; i++) {
c[a[i]/num%(k+1)]++;
} //此时c[i]中存的是a中有几个数的d位为i
for (i=1; i<=k; i++) {
c[i] += c[i-1];
} //此时c[i]中存的是a中有几个数的d位<=i
for (i=n-1; i>=0; i--) {
b[c[a[i]/num%(k+1)]-1] = a[i]; //将a[i]放入b中正确的位置
c[a[i]/num%(k+1)]--; //调整b的位置指针,避免相同元素放到相同位置
}
memcpy(a, b, sizeof(int)*n); //将排序结果复制到a中
free(c);
free(b);
}
void radix_sort(int a[], int n)
{
int i, max_value, max_digit;
int radix = 10;
/* 找出数组a中的最大值 */
max_value = a[0];
for (i=1; i<n; i++)
if (a[i] > max_value) max_value = a[i];
/* 计算最大位数 */
max_digit = 0;
while (max_value) {
max_digit++;
max_value /= radix;
}
for (i=0; i<max_digit; i++)
count_sort_radix(a, n, radix-1, i);
}
为了验证此函数的效果,加上了如下辅助代码,对3个数组进行排序,运行结果在最后,可见排序成功。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20
void show_array(int a[], int n);
void radix_sort(int a[], int n);
void main()
{
int array1[SIZE_ARRAY_1]={11,224,32,9,0};
int array2[SIZE_ARRAY_2]={1000,53,221,1,229,222};
int array3[SIZE_ARRAY_3];
for(int i=0; i<SIZE_ARRAY_3; i++) {
array3[i] = (int)((10000.0*rand())/(RAND_MAX+1.0));
}
printf("Before sort, ");
show_array(array1, SIZE_ARRAY_1);
radix_sort(array1, SIZE_ARRAY_1);
printf("After sort, ");
show_array(array1, SIZE_ARRAY_1);
printf("Before sort, ");
show_array(array2, SIZE_ARRAY_2);
radix_sort(array2, SIZE_ARRAY_2);
printf("After sort, ");
show_array(array2, SIZE_ARRAY_2);
printf("Before sort, ");
show_array(array3, SIZE_ARRAY_3);
radix_sort(array3, SIZE_ARRAY_3);
printf("After sort, ");
show_array(array3, SIZE_ARRAY_3);
}
void show_array(int a[], int n)
{
if(n>0)
printf("This array has %d items: ", n);
else
printf("Error: array size should bigger than zero.\n");
for(int i=0; i<n; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
运行结果:
Before sort, This array has 5 items: 11 224 32 9 0
After sort, This array has 5 items: 0 9 11 32 224
Before sort, This array has 6 items: 1000 53 221 1 229 222
After sort, This array has 6 items: 1 53 221 222 229 1000
Before sort, This array has 20 items: 8401 3943 7830 7984 9116 1975 3352 7682 2777 5539 4773 6288 3647 5134 9522 9161 6357 7172 1416 6069
After sort, This array has 20 items: 1416 1975 2777 3352 3647 3943 4773 5134 5539 6069 6288 6357 7172 7682 7830 7984 8401 9116 9161 9522
分析
时间复杂度
从代码可见,若要排序的数位数为d,则 radix_sort 中调用d次 count_sort_radix,而计数排序的时间复杂度为 O ( n ) O(n) O(n),所以基数排序的时间复杂度为 O ( d n ) O(dn) O(dn),因d为常数,所以基数排序的时间复杂度为 O ( n ) O(n) O(n)。
虽然其时间复杂度为 O ( n ) O(n) O(n),但隐藏在其后的常数因子一般较大,所以实际应用中选择什么样的排序算法还要视情况而定。
空间复杂度
因为基数排序调用计数排序,而计数排序的空间复杂度为 O ( n ) O(n) O(n),所以基数排序的空间复杂度为 O ( n ) O(n) O(n)。
稳定性
稳定。