我们回想一下我们小时候是怎么学习比较数字大小的?我们是先比位数,如果一个位数比另一个位数多,那这个数肯定更大。如果位数同样多,就按位数递减依次往下进行比较,哪个数在这一位上更大那就停止比较,得出这个在这个位上数更大的数字整体更大的结论。当然我们也可以从最小的位开始比较,这其实就对应了基数排序里的MSD(most
significant digital)和LSD(least significant digital)两种排序方式。
想清楚了这一点之后,我们就要考虑如何存储每一位排序结果的问题了,首先既然作为分配式排序,联想计数排序,每一位排序时存储该次排序结果的数据结构应该至少是一个长度为10的数组(对应十进制该位0-9的数字)。同时可能存在以下情况:原数组中所有元素在该位上的数字都相同,那一维数组就没法满足我们的需要了,我们需要一个10*n(n为数组长度)的二维数组来存储每次位排序结果。熟悉计数排序结果的读者可能会好奇:为什么不能像计数排序一样,在每个位置只存储出现该数字的次数,而不存储具体的值,这样不就可以用一维数组了?这个我们不妨先思考一下,在对基数排序分析完之后再来看这个问题。
现在我们可以存储每次位排序的结果了,为了在下一位排序前用到这一位排序的结果,我们要将桶里排序的结果还原到原数组中去,然后继续对更改后的原数组执行前一步的位排序操作,如此循环,最后的结果就是数组内元素先按最高位排序,最高位相同的依次按下一位排序,依次递推。得到排序的结果数组。
算法过程解析:
- 初始化:构造一个10*n的二维数组,一个长度为n的数组用于存储每次位排序时每个桶子里有多少个元素。
- 循环操作:从低位开始(我们采用LSD的方式),将所有元素对应该位的数字存到相应的桶子里去(对应二维数组的那一列)。然后将所有桶子里的元素按照桶子标号从小到大取出,对于同一个桶子里的元素,先放进去的先取出,后放进去的后取出(保证排序稳定性)。这样原数组就按该位排序完毕了,继续下一位操作,直到最高位排序完成。
我们看一下算法的原理图,
我们现有一个数组:73, 22, 93, 43, 55, 14, 28, 65, 39, 81
下面是排序过程(二维数组里每一列对应一个桶,因为桶空间没用完,因此没有将二维数组画全):
按第一位排序后数组结果:
81,22,73,93,43,14,55,65,28,39
可以看到数组已经按个位排序了。
2根据个位排序结果按百位排序
取出排序结果:
14,22,28,39,43,55,65,73,81,93
可以看到在个位排序的基础上,百位也排序完成(对于百位相同的数子,如22,28,因为个位已经排序,而取出时也保持了排序的稳定性,所以这两个数的位置前后是根据他们个位排序结果决定的)。因为原数组元素最高只有百位,原数组也完成了排序过程。
弄清基数排序的过程后,我们来看看这个算法的时间复杂度是多少?每次循环遍历数组将元素放在指定位置Θ(n),在从桶中取出数据Θ(n),循环d次(d是位数),时间复杂度就是Θ(r*n)
下面看具体的代码实现过程,具体的实现思路大概分为3个部分,
1、获取待排序数组中的最大数的位数
2、构建二维数组,第一个数组当桶,用于放置从0到9的位数的数据,比如12,45,67,我们以个位数进行筛选的适合,2就放在2的位置的桶里,
3、二维数组的第二个数组用于放置每个桶的具体的数据,
4、遍历原数组数据,从低位到高位,每次把对应位置的数据放到指定编号的桶里,然后按照桶的顺序取出数据,重复进行…
/**
* 基数排序工具类
* @param array 待排序数组
* @return
*/
private static int[] radixSort(int[] array) {
int maxNum = 0;
for(int i=0;i<array.length;i++){
if(array[i] > maxNum){
maxNum = array[i];
}
}
//获取待排序数组最大数的最高位
int d = 0;
if(maxNum != 0){
int num = String.valueOf(maxNum).length();
d = (int)Math.pow(num,10);
}
int n = 1;//代表位数对应的数:1,10,100...
int k = 0;//保存每一位排序后的结果用于下一位的排序输入
int length = array.length;
//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int[][] bucket = new int[10][length];
int[] order = new int[length];//用于保存每个桶里有多少个数字
while (n < d) { //[12,45,67,43,32,89,18]
for (int num : array){ //将数组array里的每个数字放在相应的桶里
int digit = (num / n) % 10;
bucket[digit][order[digit]] = num;
order[digit]++;
}
//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
for (int i = 0; i < length; i++){
//这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
if (order[i] != 0){
for (int j = 0; j < order[i]; j++) {
array[k] = bucket[i][j];
k++;
}
}
//将桶里计数器置0,用于下一次位排序
order[i] = 0;
}
n *= 10;
k = 0;//将k置0,用于下一轮保存位排序结果
}
return array;
}
我们再看第二种写法,使用双重List,效果差不多,
public static int[] basicSort(int[] arr) {
if (arr == null || arr.length < 2) {
return arr;
}
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int maxDigit = 0; // 确定最大的数字有几位
while (max != 0) {
max /= 10;
maxDigit++;
}
int mod = 10; // 每一位的倍数差距
int div = 1; // 定义一个除法的基准值
//这里也可以使用二维数组,外集合表示桶,内集合表示每个桶放入的数据
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<ArrayList<Integer>>(); // 定义一个桶
for (int i = 0; i < 10; i++) {// 看图说话,每一位的值都在0-9之间
bucketList.add(new ArrayList<Integer>());
}
for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
for (int j = 0; j < arr.length; j++) {
int num = (arr[j] % mod) / div;// 第一次循环,取出个位数字进行一个填充
bucketList.get(num).add(arr[j]);
}
int index = 0;
// 回填的操作
for (int j = 0; j < bucketList.size(); j++) {
for (int k = 0; k < bucketList.get(j).size(); k++) {
arr[index++] = bucketList.get(j).get(k);
}
bucketList.get(j).clear();
}
}
return arr;
}
运行一下测试,可以看到已经成功排序,
public static void main(String[] args) {
int[] arr = {12,54,67,33,19,89,102,666,98,45,54};
System.out.println(Arrays.toString(radixSort(arr)));
}
以上两种方式都经过测试,可以放心使用!感谢观看