基数排序是对桶排序的一个扩展,是一个典型的以空间换时间的算法,其算法的实现附带着额外的空间开销
核心算法如下:(图上收集个位那里19应该放到下标为9的桶,图片有误)
我们需要借助10个桶来完成排序(可以用二维数组,也可以用链表数组来实现)
以图上待排序的数组{135,242,192,93,345,11,24,19}
为例来说明这个算法的实现过程
初始化10个桶,每个桶的容量为待排序数组长度8,用二维数组来实现
数组第一轮遍历,取出各个元素的个位数,放入相应的桶: 135取出5,放入下标为5的桶中:
bucket[5] = 135
242取出2,bucket[2]=242
…数组第一轮遍历完成后,桶中已经存放了对应的元素
[{0,0,0,0,0,0,0,0},{11,0,0,0,0,0,0,0},{242,192,0,0,0,0,0,0},{93,0,0,0,0,0,0,0},{24,0,0,0,0,0,0,0},{135,345,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{19,0,0,0,0,0,0,0}]
把这些桶中的元素按顺序重新放回待排序数组中,得到最新数组为
{11,242,192,93,24,135,345,19}
清空每个桶中的元素
数组第二遍遍历,取出各个元素的十位数,放入相应的桶: 11取出1,
bucket[1] = 11
242取出4,bucket[4] = 242
192取出9,bucket[9] = 192
…第二遍遍历结束后,把这些桶中的元素按顺序重新放回待排序数组中,得到最新数组为
{11,19,24,135,242,345,192,93}
清空每个桶中的元素
数组第三遍遍历,取出各个元素的百位数… … 直到循环了最长位数(该数组里边元素最长位数为百位,所以需要循环遍历三次数组)遍后数组就有序了
算法代码(桶用二维数组实现)
public class BaseNumberSortByArray {
private static final int BUCKET_SIZE = 10;//桶的数量
private static int[] myArr; //原始数组
private static int[][] bucket;//桶
private static int[] elementCount;//每个桶真实所存放的元素数量
private BaseNumberSortByArray() {}
public static BaseNumberSortByArray instance(int[] arr) {
myArr = arr;
initBucket(arr.length);
return new BaseNumberSortByArray();
}
/**
*
* @Title: initBucket
* @Description: 初始化桶和每个桶目前所存放的元素数量为0
* @param: @param length 每个桶的容量
* @return: void
*/
private static void initBucket(int length) {
bucket = new int[BUCKET_SIZE][length];
elementCount = new int[BUCKET_SIZE];
}
/**
*
* @Title: getMaxLength
* @Description: 获取所有元素中位数的最大值
* @return: int 位数的最大值
*/
private int getMaxLength() {
int length = myArr.length;
int max = myArr[0];
for(int i = 1; i < length; i++) {
if(max < myArr[i])max = myArr[i];
}
return (max + "").length();
}
/**
*
* @Title: baseNumberSort
* @Description: 基数排序入口
* @return: void
*/
public void baseNumberSort() {
/**
* 算法:
* 1.循环获取各个元素的个位数,该个位数作为下标将元素存放入相应的桶中(如元素为32,个位数为2,那么就将32存放入下标为2的桶中)
* 2.按顺序遍历所有桶,将桶中的元素按顺序取出放回原始数组中
* 3.清空桶中的元素计数
* 4.循环获取各个元素的百位数,该百位数作为下标将元素存放入相应的桶中
* 5. ....依次循环,直到取到最长位数
*/
int maxLength = getMaxLength();
int myArrSize = myArr.length;
int tempNumber = -1;//取出的当前位数值
//1,4...
for(int i = 0; i < maxLength; i++) {
for(int j = 0; j < myArrSize; j++) {
tempNumber = (myArr[j] / (int)Math.pow(10, i)) % 10;//取出当前位数
bucket[tempNumber][elementCount[tempNumber]] = myArr[j];//该位数作为下标将元素存放入相应的桶中
elementCount[tempNumber]++;//桶中元素计数加一
}
//2....
//按顺序遍历所有桶,将桶中的元素按顺序取出放回原始数组中
int k = 0;//遍历桶元素存入原始数组所使用的指向原始数组的当前下标
//遍历每个桶
for(int ii = 0; ii < BUCKET_SIZE; ii++) {
//遍历每个桶中的元素
for(int jj = 0; jj < elementCount[ii]; jj++) {
myArr[k] = bucket[ii][jj];
k++;
}
//3...
//当前桶中元素的计数置为0
elementCount[ii] = 0;
}
}
}
}
算法代码(桶用链表数组实现)
class BucketNode{
int num;
BucketNode node;
BucketNode(int num){
this.num = num;
}
}
public class BaseNumberSortByLinkList {
private static final int BUCKET_SIZE = 10;//桶的数量
private static int[] myArr; //原始数组
private static BucketNode[] bucket;//桶
private BaseNumberSortByLinkList() {}
public static BaseNumberSortByLinkList instance(int[] arr) {
myArr = arr;
initBucket();//初始化桶
return new BaseNumberSortByLinkList();
}
private static void initBucket() {
bucket = new BucketNode[BUCKET_SIZE];
for(int i = 0; i < BUCKET_SIZE; i++) {
bucket[i] = new BucketNode(-1);
}
}
/**
*
* @Title: getEndBucketNode
* @Description: 获得指定桶的最后一个元素
* @param: @param index 指定的桶的下标
* @return: BucketNode 最后一个元素
*/
private BucketNode getEndBucketNode(int index) {
BucketNode bucketNode = bucket[index];
while(bucketNode.node != null)bucketNode = bucketNode.node;
return bucketNode;
}
/**
*
* @Title: baseNumberSort
* @Description: 基数排序入口
* @return: void
*/
public void baseNumberSort() {
/**
* 算法:
* 1.循环获取各个元素的个位数,该个位数作为下标将元素存放入相应的桶中(如元素为32,个位数为2,那么就将32存放入下标为2的桶中)
* 2.按顺序遍历所有桶,将桶中的元素按顺序取出放回原始数组中
* 3.清空桶中的元素计数
* 4.循环获取各个元素的百位数,该百位数作为下标将元素存放入相应的桶中
* 5. ....依次循环,直到取到最长位数
*/
int maxLength = getMaxLength();
int myArrSize = myArr.length;
int tempNumber = -1;//取出的当前位数值
//1,4...
for(int i = 0; i < maxLength; i++) {
for(int j = 0; j < myArrSize; j++) {
tempNumber = (myArr[j] / (int)Math.pow(10, i)) % 10;//取出当前位数
BucketNode endBucketNode = getEndBucketNode(tempNumber);
endBucketNode.node = new BucketNode(myArr[j]);//该位数作为下标将元素存放入相应的桶中
}
//2....
//按顺序遍历所有桶,将桶中的元素按顺序取出放回原始数组中
BucketNode bucketNode = null;
int k = 0;//遍历桶元素存入原始数组所使用的指向原始数组的当前下标
//遍历每个桶
for(int ii = 0; ii < BUCKET_SIZE; ii++) {
//遍历每个桶中的元素
bucketNode = bucket[ii].node;
while(bucketNode != null) {
myArr[k] = bucketNode.num;
k++;
bucketNode = bucketNode.node;
}
//3...
//清空当前桶中的元素
clearBucket(ii);
}
}
}
/**
*
* @Title: clearBucket
* @Description: 清空指定桶的所有元素
* @param: @param index 指定桶的下标
* @return: void
*/
private void clearBucket(int index) {
bucket[index].node = null;
}
/**
*
* @Title: getMaxLength
* @Description: 获取所有元素中位数的最大值
* @return: int 位数的最大值
*/
private int getMaxLength() {
int length = myArr.length;
int max = myArr[0];
for(int i = 1; i < length; i++) {
if(max < myArr[i])max = myArr[i];
}
return (max + "").length();
}
}
时间复杂度:O(n*k)
空间复杂度:O(n+k)
稳定性:稳定
ps: n为数据规模, k为桶的个数
基数排序的缺点:
- 当数据规模很大时,可能会产生oom问题,没有足够的内存空间可以初始化桶
- 基础的基数排序不支持负数排序
源码地址github地址