6.9 基数排序
6.9.1 基数排序介绍
- 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort),顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用基数排序(Radix Sort)是桶排序的扩展
- 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
6.9.2 基本思想
对于一组待排序数组,将所有数值统一为长度一样的数位,数位较少者高位补零。然后从最低位依次开始,每次取出待排序数的每一位值,并将该位数对应的数值存入到对应的桶中,随后遍历每个桶,将数据顺序放回至数组中,重复上述过程,直到最高位,数列就可以成为一个有序数组。
以数组:{12,33,11,42,189,174,325,456,37,75,88}为例,下面演示基数排序的流程:
第一次循环,遍历元素,找到每个元素的个位数字,并将元素值对应存放到桶中,随后遍历每个桶,将桶中元素依次放入到原数组中:
第二次循环,遍历数组的元素,找到每个元素的十位数,并将元素值对应存放到桶中,随后遍历每个桶,将桶中元素依次放入到原数组中:
第三轮:遍历数组的元素,找到每个元素的百位数字,并将元素值对应存放到桶中,随后遍历每个桶,将桶中元素依次放入到原数组中:
由于整个数组中最大的元素为百位数字,因此只需遍历三次,找到百位即可。
从上述过程中可以发现,在一些情况中,可能有些桶中元素一直为0,其他桶可能会出现数据集中现象。为了保证桶中数据不溢出,在初始化时,需要定义每个桶的初始容量均为数组长度大小。总共需要10个桶。而循环遍历的次数为整个数组中最大元素的有效位数次;
因此可以发现,整个过程的时间复杂度没有很高,但当数据量较大时,会有极高的内存开销,这就是典型的以空间换时间的排序算法。
6.9.3 代码实现细节
package com.kevin.sortAlgorithm;
import java.util.Arrays;
/**
* @author : kevin ding
* @date : 2022/3/7 22:52
* @description : 基数排序思想:
初始设置编号从0到9的10个桶,将每个数的个位数取出,判断其值,并将数放到对应的桶中。存放完毕顺序将每个桶中的元素依次取出,放到原数组中;第二次,将数组中每个数的十位数,取出并将数放到对应编号的桶中,存放完毕后将每个桶中的元素取出,放回原数组;依次执行
*/
public class RadixSortDemo {
public static void main(String[] args) {
int[] array = {12,33,11,42,189,174,325,456,37,75,88};
radixSortAnalysis(array);
}
public static void radixSortAnalysis(int[] array){
// 定义一个二维数组,表示10个桶,其中每个桶就是一个一维数组
// 为了防止桶中数据溢出,每个一维数组(桶)大小定为array.length
int[][] bucket = new int[10][array.length];
// 为了方便记录每个桶中存放了多少数,定义一个一维数组,每个位置的值表示下标对应的桶中存放的数值个数
// elementCount[1] 记录的是bucket[1]这个桶中存放数据的个数
int[] elementCounts = new int[10];
// 第1轮处理 取出元素的个位数,放置到对应的桶中
for(int j = 0; j<array.length; j++){
// 取出每个元素的个位元素 数值
int digitValue = array[j] /1 % 10;
// 放入到对应的桶中
bucket[digitValue][elementCounts[digitValue]] = array[j];
// 每次放入完之后,元素个数加 1
elementCounts[digitValue] += 1;
}
// array遍历完之后,需要按照桶的顺序取出数据,放入到原数组中
int index = 0;
// 遍历每一个桶中的元素,存放到数组中
for (int j = 0; j < elementCounts.length; j++) {
// 如果该桶中数据个数不为0,则遍历并将元素放入到原数组array中
if(elementCounts[j] != 0){
// 循环遍历该桶
for (int k = 0; k < elementCounts[j]; k++) {
// 取出元素 放入到原数组中
array[index] = bucket[j][k];
// 随后将index后移
index += 1;
}
}
// 在每次处理完,需要将每个elementCounts的计数置为0
elementCounts[j] = 0;
}
System.out.println("第1轮处理过后,array:" + Arrays.toString(array));
System.out.println("=-=-==============================");
// 第2轮处理 取出元素的十位数,放置到对应的桶中
for(int j = 0; j<array.length; j++){
// 取出每个元素的个位元素 数值
int digitValue = array[j] /10 % 10;
// 放入到对应的桶中
bucket[digitValue][elementCounts[digitValue]] = array[j];
// 每次放入完之后,元素个数加 1
elementCounts[digitValue] += 1;
}
// array遍历完之后,需要按照桶的顺序取出数据,放入到原数组中
index = 0;
// 遍历每一个桶中的元素,存放到数组中
for (int j = 0; j < elementCounts.length; j++) {
// 如果该桶中数据个数不为0,则遍历并将元素放入到原数组array中
if(elementCounts[j] != 0){
// 循环遍历该桶
for (int k = 0; k < elementCounts[j]; k++) {
// 取出元素 放入到原数组中
array[index] = bucket[j][k];
// 随后将index后移
index += 1;
}
}
// 在每次处理完,需要将每个elementCounts的计数置为0
elementCounts[j] = 0;
}
System.out.println("第2轮处理过后,array:" + Arrays.toString(array));
System.out.println("=-=-==============================");
// 第3轮处理 取出元素的百位数,放置到对应的桶中
for(int j = 0; j<array.length; j++){
// 取出每个元素的个位元素 数值
int digitValue = array[j] /100 % 10;
// 放入到对应的桶中
bucket[digitValue][elementCounts[digitValue]] = array[j];
// 每次放入完之后,元素个数加 1
elementCounts[digitValue] += 1;
}
// array遍历完之后,需要按照桶的顺序取出数据,放入到原数组中
index = 0;
// 遍历每一个桶中的元素,存放到数组中
for (int j = 0; j < elementCounts.length; j++) {
// 如果该桶中数据个数不为0,则遍历并将元素放入到原数组array中
if(elementCounts[j] != 0){
// 循环遍历该桶
for (int k = 0; k < elementCounts[j]; k++) {
// 取出元素 放入到原数组中
array[index] = bucket[j][k];
// 随后将index后移
index += 1;
}
}
// 在每次处理完,需要将每个elementCounts的计数置为0
elementCounts[j] = 0;
}
System.out.println("第3轮处理过后,array:" + Arrays.toString(array));
}
}
6.9.4 基数排序
public static void radixSort(int[] array){
// 需要定义一个最大值变量,记录最大元素,获取其位数
int maxValue = array[0];
for (int ele : array) {
if(maxValue <= ele){
maxValue = ele;
}
}
// 最大值的位数
int maxLength = (maxValue + "").length();
// 定义一个二维数组,表示10个桶,其中每个桶就是一个一维数组
// 为了防止桶中数据溢出,每个一维数组(桶)大小定为array.length
int[][] bucket = new int[10][array.length];
// 为了方便记录每个桶中存放了多少数,定义一个一维数组,每个位置的值表示下标对应的桶中存放的数值个数
// elementCount[1] 记录的是bucket[1]这个桶中存放数据的个数
int[] elementCounts = new int[10];
// 需要遍历最大值位数 次,若最大值为百位,需要遍历三次
for (int i = 0; i < maxLength ; i++) {
for(int j = 0; j<array.length; j++){
// 取出每个元素的个位元素 数值
int digitValue = array[j] /(1* (int) Math.pow(10, i)) % 10;
// 放入到对应的桶中
bucket[digitValue][elementCounts[digitValue]] = array[j];
// 每次放入完之后,元素个数加 1
elementCounts[digitValue] += 1;
}
// array遍历完之后,需要按照桶的顺序取出数据,放入到原数组中
int index = 0;
// 遍历每一个桶中的元素,存放到数组中
for (int j = 0; j < elementCounts.length; j++) {
// 如果该桶中数据个数不为0,则遍历并将元素放入到原数组array中
if(elementCounts[j] != 0){
// 循环遍历该桶
for (int k = 0; k < elementCounts[j]; k++) {
// 取出元素 放入到原数组中
array[index] = bucket[j][k];
// 随后将index后移
index += 1;
}
}
// 在每次处理完,需要将每个elementCounts的计数置为0
elementCounts[j] = 0;
}
}
System.out.println("基数排序过后,array:" + Arrays.toString(array));
}
总结:
-
基数排序是对传统桶排序的扩展,速度很快.
-
上述分析不难发现,基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成
OutOfMemoryError
。 -
基数排序时稳定的。
-
排序的稳定性介绍:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。