基数排序
①简介
基数排序是一种用在卡片排序机上的算法,一张卡片有80列,在每一列上机器可以选择在12个位置中的任意一处穿孔。通过机器的操作,能够“编程”来检查每个卡片中的给定列,然后根据穿孔的位置将他们分别放在12个容器中。操作员就可以挨个容器来收集卡片,其中第一个位置穿孔的卡片在最上面,以此类推。
②原理
直观上来看,可能会认为应该先按照最高有效位进行排序并分装到不同的容器中,再对每个容器进行递归的排序,但是对一个容器排序的同时,其他容器全部是空闲状态,必须放在一边还要存起来,会花费很大的空间。
基数排序的思想恰好和人们的直观感受相反,是先按最低有效位进行排序来解决问题的。按最低有效位排序并分配好容器,再将容器中的卡片按照容器编号合成一叠,0号容器在1号容器上面,依次类推。合成一堆后再依据次最低有效位循环上面的流程。
③C语言实现
#define NUM 10
int digit_assist[] = { 1, 10, 100, 1000, 10000 };
//清空辅助数组
void clear_array(int* a, int count)
{
for (int i = 0; i < count; i++)
{
a[i] = 0;
}
}
//得到第d位数上的数字
int get_digit(int num, int d)
{
return (num / digit_assist[d - 1]) % 10;
}
//得到输入数组中最大的位数
int get_max_digit(int *a, int count)
{
int max_digit = 1,max = a[0];
for (int i = 1; i < count; i++)
{
if (a[i] > max)
max = a[i];
}
while (0 != max / 10)
{
max = max / 10;
max_digit++;
}
return max_digit;
}
//基数排序
void radix_sort(int* a, int count)
{
int max_digit, *bucket, *bucket_index;
bucket = (int*)malloc(count * sizeof(int));
bucket_index = (int*)malloc(NUM * sizeof(int));
max_digit = get_max_digit(a, count);
for (int i = 0; i < max_digit; i++)
{
//清空辅助容器
clear_array(bucket, count);
clear_array(bucket_index, NUM);
/* 利用了计数排序的小知识,避免过大的容器开销
* 先统计每个桶里面有多少个数字
*/
for (int j = 0; j < count; j++)
bucket_index[get_digit(a[j], i + 1)]++;
for (int j = 1; j < NUM; j++)
bucket_index[j] += bucket_index[j - 1];
/* 开始进行排序
* 这里倒序开始,此处重点,原因在后面分析
*/
for (int j = count - 1; j >= 0; j--)
{
int k = get_digit(a[j], i + 1);
bucket[bucket_index[k] - 1] = a[j];
bucket_index[k]--;
}
//再从容器中收集排列的数据
for (int j = 0; j < count; j++)
{
a[j] = bucket[j];
}
}
}
void main()
{
int count, *p;
printf("请输入需要排序的数的个数 :");
scanf_s("%d", &count);
p = (int*)malloc(count * sizeof(int));
printf("\n请输入需要排序的%d个数字:",count);
for (int i = 0; i < count; i++)
{
scanf_s("%d", p+i);
}
printf("\n快速排序后的数组:");
radix_sort(p,count);
for (int i = 0; i < count; i++)
{
printf("%d ", p[i]);
}
printf("\n");
system("pause");
}
在这里说明一下,代码里面开始排序的时候为什么从后面提取数据而不是从前面。这里用一个例子来说明情况:假设我们需要排列的数组有三个数,分别是333,22,1。这三个数需要三次大的基数排序循环才能排好序,在循环的前两次,按照流程走下来都没什么大的问题,第二次大循环下来后,原始数组变成了1,22,333。
现在进行第三次的排序,轮到比较百位上的数字了,分别是百位上数字为0,0,3。统计出来的bucket_index数组中结果为2,2,2,3,3,3,3,3,3,3。统计完成后开始排序,如果这时候从前往后开始重新放置数字进入新的数组(类似计数排序那样bucket_index[k]–;),具有相同的百位数字中,十位数字较小的数字可能被放到十位数字较大的数字后面,会造成之前十位数字大循环拍好序列的紊乱。因此需要从后面向前面取数。