什么是基数排序?
基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。12
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。12
LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序或计数排序。12
MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序或递归的基数排序。12
基数排序的过程
以LSD为例,假设我们要对数组 arr = {53, 3, 542, 748, 14, 214, 154, 63, 616} 进行基数排序,其过程如下:
第一步:按个位进行排序
首先,我们需要准备10个桶(0-9),然后根据数组中每个元素的个位数字,将其放入对应的桶中。
例如,53的个位是3,所以放入第3个桶中;542的个位是2,所以放入第2个桶中;以此类推。
接着,我们按照桶的顺序,依次将桶中的元素取出,并放回原数组中。
例如,第0个桶中没有元素,所以跳过;第1个桶中有一个元素3,所以将3放入原数组的第一个位置;第2个桶中有两个元素542和214,所以将它们依次放入原数组的第二和第三位置;以此类推。
这样,我们就完成了按个位进行排序的过程。
第二步:按十位进行排序
接下来,我们重复第一步的操作,只不过这次是根据数组中每个元素的十位数字来分配桶。
例如,3的十位是0,所以放入第0个桶中;53的十位是5,所以放入第5个桶中;以此类推。
然后,我们同样按照桶的顺序,依次将桶中的元素取出,并放回原数组中。
例如,第0个桶中有两个元素3和14,所以将它们依次放入原数组的第一个和第二位置;第1个桶中有一个元素616,所以将616放入原数组的第三位置;以此类推。
这样,我们就完成了按十位进行排序的过程。
第三步:按百位进行排序
最后,我们再次重复第一步的操作,只不过这次是根据数组中每个元素的百位数字来分配桶。
例如,3的百位是0,所以放入第0个桶中;616的百位是6,所以放入第6个桶中;以此类推。
然后,我们同样按照桶的顺序,依次将桶中的元素取出,并放回原数组中。
例如,第0个桶中有四个元素3, 14, 53, 63,所以将它们依次放入原数组的第一个到第四位置;第1个桶中有一个元素154,所以将154放入原数组的第五位置;以此类推。
这样,我们就完成了按百位进行排序的过程。
由于数组中最大的数是748,只有三位数,所以我们不需要再进行更高位的排序了。此时,原数组已经变成了一个有序序列。
基数排序的代码实现
下面是用JAVA语言实现基数排序的代码:
public class RadixSort {
// 辅助函数,求数据的最大位数
public static int maxBit(int[] data) {
int max = data[0]; // 假设第一个数是最大数
for (int i = 1; i < data.length; i++) { // 遍历找出最大数
if (data[i] > max) {
max = data[i];
}
}
int d = 1; // 最大位数
int p = 10; // 基数
while (max >= p) { // 循环判断最大数有几位
max /= 10;
d++;
}
return d;
}
// 基数排序
public static void radixSort(int[] data) {
int n = data.length; // 数组长度
int d = maxBit(data); // 最大位数
int[][] bucket = new int[10][n]; // 桶数组,二维数组,每个桶是一个一维数组
int[] count = new int[10]; // 记录每个桶中存放了多少个元素
int radix = 1; // 基数,从个位开始,依次乘以10
for (int i = 0; i < d; i++) { // 进行d轮排序
for (int j = 0; j < n; j++) { // 遍历数组,按照当前位数分配到对应的桶中
int k = (data[j] / radix) % 10; // 计算当前位数的数字
bucket[k][count[k]] = data[j]; // 将元素放入桶中
count[k]++; // 桶中元素个数加一
}
int index = 0; // 记录取出元素的位置
for (int k = 0; k < 10; k++) { // 遍历每个桶,按照顺序取出元素
if (count[k] != 0) { // 如果桶中有元素才取出
for (int l = 0; l < count[k]; l++) { // 遍历桶中的元素
data[index++] = bucket[k][l]; // 取出元素放回原数组
}
count[k] = 0; // 取出后将桶中元素个数置为0,方便下一轮使用
}
}
radix *= 10; // 基数乘以10,进入下一位的排序
}
}
public static void main(String[] args) {
int[] data = {53, 3, 542, 748, 14, 214, 154, 63, 616};
System.out.println("排序前:" + Arrays.toString(data));
radixSort(data);
System.out.println("排序后:" + Arrays.toString(data));
}
}
输出结果:
排序前:[53, 3, 542, 748, 14, 214, 154, 63, 616]
排序后:[3, 14, 53, 63, 154, 214, 542, 616, 748]
基数排序的复杂度分析
时间复杂度:O(d * (n + r)),其中d是最大数的位数,n是数组长度,r是基数(桶的数量)。
空间复杂度:O(n + r),其中n是数组长度,r是基数(桶的数量)。
稳定性:稳定,因为每一轮排序都是按照相同的位数进行比较,不会改变相同元素的相对顺序。
基数排序的优缺点
优点:
- 适用于整数排序,尤其是位数较多的情况。
- 稳定,不会改变相同元素的相对顺序。
- 时间复杂度较低,当d较小且r较大时,接近于线性时间。
缺点:
- 需要额外的空间来存储桶和计数器。
- 不适用于小数或负数的排序。
- 对于位数较少或者范围较小的整数,可能不如其他排序算法快。
参考资料
: 最详细的【基数排序】—排序算法,思路清晰动图保姆级讲解,五分钟搞懂! _Steve_hanhaiLong的博客-CSDN博客