桶排序:空间换时间的典型代表
背景:最近在学习排序算法时,听韩顺平老师的课发现,往小桶放数据的那段代码搞不清楚,本着求真务实的态度去debug了一下(其实就是脑袋转不过来弯儿),才突然悟了,下面把出现问题的地方和之后的桶排序流程拿出来分析一下,在提供思路参考的同时,顺便当作一个教训,好好吸取。
问题:
当时想不透的地方主要是第一轮排序中的下面这段代码:
for(int i = 0 ; i<arr.length;i++) {
int digit = arr[i] % 10;
bucket[digit][bucketelementcount[digit]]=arr[i];
bucketelementcount[digit]++;
}
在这段代码中,始终搞不懂两个地方
1.二维数组bucket的列中不是存放着arr[]数组中的数据,它怎么能够代表存放到二维数组bucket的对应下标的数值的位数
2.为什么二维数组中的列中数据要加1,这个也没法表示这个桶中数的个数
解决图解:
当时按着这个第一轮排序的代码debug了一下,发现我太天真了,具体结果如下图:
上面图片最左边的是一维数组bucketeelementcount最终存放的结果,小桶号就是二维数组的行,横放小桶其实可以看成小桶横放在地面上,再依次摞起来
回过头来看问题:可以发现应该是数学的函数没学好,自变量与函数分不清
对于问题一:二维数组bucket的列中的一维数组初始时的默认值为0,即bucketelementcount[digit]的值无论刚开始传入的digit是啥,bucketelementcount[digit]总是等于0。
对于第二个问题也就是说这里并不是将原数组中的数据传进来,而是统计出小桶里有的个数。这个统计出来的个数在后面取出小桶数据时就派上用场了。
完整代码(含有详细注释):
提示:可以先看一下第一轮的遍历方式,或者再看一轮遍历方式,最后归纳出整个遍历过程。
//测试
public static void main(String[] args) {
int[] arr = {53,3,542,748,14,214};
// radixsortfirst(arr);
radixsort(arr);
}
/**
* 先以第一次排序为引入,后再写总的排序方法
* 下面第一轮排序radixsortfirst,最后总排序为radixsort
* @param arr
*/
public static void radixsortfirst(int[] arr) {
//有一个二维数组存放每次存储的数据。
int[][] bucket = new int[10][arr.length];
//定义一个一维数组来表示每个小桶里的存放的值的个数
int[] bucketelementcount = new int[10];
//先从个位进行遍历放入
for(int i = 0 ; i<arr.length;i++) {
int digit = arr[i] % 10;
bucket[digit][bucketelementcount[digit]]=arr[i];//这里bucketelementcount[digit]可以看作计数
bucketelementcount[digit]++;
}
//经过上面的遍历,我们能知道,小桶里存放了按个位排序的数值,而一维数组中放了每个小小桶里的数值个数,现在要将排好序的数值放入数组中。
int index = 0;
for(int i = 0 ; i<bucketelementcount.length;i++) {
//上面的循环实际上是遍历每个小桶
if(bucketelementcount[i] != 0) {
//实际上计数的不为0
//下面开始把小桶里的数放回原来的数组中
for(int j=0;bucketelementcount[i]>j;j++) {
arr[index++] = bucket[i][j];
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void radixsort(int[] arr) {
//确定最大数的位数,因为要按个位、十位、百位...的方式排
int maxnumber = arr[0];
for(int i = 0;i<arr.length;i++) {
if(arr[i]>maxnumber) {
maxnumber = arr[i];
}
}
//找到最大数后要确定它的位数
int maxlength = (maxnumber+"").length();
//接下来开始按位进行排序
for(int k = 0 ,n = 1; k< maxlength;k++,n *=10) {
//有一个二维数组存放每次存储的数据。
int[][] bucket = new int[10][arr.length];
//定义一个一维数组来表示每个小桶里的存放的值的个数
int[] bucketelementcount = new int[10];
//先从个位进行遍历放入
for(int i = 0 ; i<arr.length;i++) {
int digit = arr[i] /n % 10;
//如果按照十位进行排序,要改为arr[i]/10%10;如果按照百位来进行排序arr[i]/100%10;所以直接用arr[i] / Math.pow(10,i)%10也可以
bucket[digit][bucketelementcount[digit]]=arr[i];//这里bucketelementcount[digit]可以看作计数
bucketelementcount[digit]++;
}
//经过上面的遍历,我们能知道,小桶里存放了按个位排序的数值,而一维数组中放了每个小小桶里的数值个数,现在要将排好序的数值放入数组中。
int index = 0;
for(int i = 0 ; i<bucketelementcount.length;i++) {
//上面的循环实际上是遍历每个小桶
if(bucketelementcount[i] != 0) {
//实际上计数的不为0
//下面开始把小桶里的数放回原来的数组中
for(int j=0;bucketelementcount[i]>j;j++) {
arr[index++] = bucket[i][j];
}
}
//每次找完后就置为0,为下一轮排序准备
bucketelementcount[i]=0;
}
// 把每次输出改为最后统一输出
// System.out.println(Arrays.toString(arr));
}
System.out.println(Arrays.toString(arr));
}