常见排序算法之基数排序
原创
©著作权归作者所有:来自51CTO博客作者wx63c373b99113d的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
- 1、概述
- 2、测试代码
- 3、测试小案例
1、概述
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
核心思想:根据我们数字在不同位置上的大小,不断地循环(先看个位,然后十位,然后百位…),将对应位置上的数据进行放置,不同的数字放在不同的桶中,相同的数字放在相同的桶中。最终我们能够得到对应的有序数组
以数组 [ 2,7,11,12,312,213,14,27,37,44 ]为例:
第一次排序后的数组,当作第二次排序的数组 [ 11,12,312,2,213,14,44,37,27,7 ]:
第二次排序后的数组,当作第三次排序的数组 [ 2,7,11,12,312,213,14,27,37,44 ]:
最终的到有序的数组:2,7,11,12,14,27,37,44,213,312
补充:
- 本次待排序的数组中最大的数字为3位数,所以只需要循环三次就可以得到对应的结果
- 我们第三次循环的时候,百位上为0的数字(一位数,两位数)它们全部都放在0号桶的位置,数字会越来越集中
- 数字的极端情况为都是相同的数字,所以我们桶的大小为数组的长度
- 整个排序需要开辟的空间:(10(桶数量) * arr.length * 4(字节) / 1024 / 1024 / 1024 )G
- 当数据量比较大的时候,上千万的数据(具体的大小根据自己电脑的内存而定)时会出现OOM
2、测试代码
核心步骤梳理:
1、找到我们的最大值,确定该数字的位数,为了后期的循环次数做准备
2、定义相应的参数:设置我们的桶及桶中的位置(二维数组),对应桶中的数字个数
3、遍历我们数组中的数字,根据对应位上(个位、十位…)的值,放到对应的桶中
4、不断的循环,直至循环完成
5、将我们桶中的数据转移到我们对应的数组中(不断地遍历我们地桶及桶中的位置)
6、遍历完成以后,及时清除桶中的数据
最大数字的位数大小,决定了整个排序算法的外层循环次数
public class RadixSortTest01 {
public static void main(String[] args) {
int[] arr = {2, 312, 44, 14, 12, 7, 213, 27, 11, 37};
radixSort(arr);
System.out.println("基数排序后的数组为:" + Arrays.toString(arr));
}
public static void radixSort(int[] arr) {
//1.找到待排序的数组中的最大的数字
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i + 1];
}
}
//2.获取到最大数字有多少位
int maxLength = (max + "").length();
//3.定义一个二维数组:相当于一个含有是个10桶位置,且每个桶的大为数组的长度(防止出现极端情况)
int[][] bucket = new int[10][arr.length];
//4.记录每一个桶里面,实际存放的数据的个数(由于有10个桶,所以我们设置数组的大小为10)
//bucketElementCounts[0]=3 : 第一个桶位置的数据个数为3个
int[] bucketElementCounts = new int[10];
//5.由个位开始依次往前遍历,循环的次数是由最大数字的位数决定的
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//6.遍历数组中的每一个数字,根据其对应位置上的数字,放到指定桶的指定位置
for (int j = 0; j < arr.length; j++) {
//7.获取到我们待排序数组指定的位置上的指定位数
int digitOfElement = arr[j] / n % 10;
//8.将我们遍历到的数字,放到桶中的相应的位置
//digitOfElement 表示具体的位置,用于指定对应的桶
//bucketElementCounts[digitOfElement] 表示根据对应的桶位置,获取到对应桶位置下面的元素信息
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
//9.前面我们添加了数字,所以我们对应桶下的数字个数需要+1
bucketElementCounts[digitOfElement]++;
}
//10.将我们之前放在桶中的数据,返回放到我们对应的数组中
int index = 0;
//11.遍历10个桶
for (int k = 0; k < bucketElementCounts.length; k++) {
if (bucketElementCounts[k] != 0) {
//12.如果桶中的数据不为空,那么我们就将对应位置上的数字,赋值到我们的原来数组中(直接覆盖之前的值)
for (int l = 0; l < bucketElementCounts[k]; l++) {
//13.进行赋值,并且index不断地+1
arr[index] = bucket[k][l];
index++;
}
}
//14.每次成功转移数据后,需要清空我们桶中对应位置的数据
bucketElementCounts[k] = 0;
}
}
}
}
不得不说,在大量数据,并且对应数据中最大值比较小的情况下(五位数的数字和八位数的数字相比,也就只多了三次循环),排序所花费的时间是十分少的。不过该排序的一个最大的弊端就是十分占用系统资源,对应的二维数组中,我们每一个桶的大小都是整个数组的长度。 该排序算法,以空间换时间。 本排序算法无法比较负数,若需要比较负数则需要再进行优化。
3、测试小案例
利用我们以有的条件,进行一个小案例的测试:计算将数组长度为80000的乱序数组排序所花费的时间
package pers.mobian.jishu;
import java.util.Arrays;
public class RadixSortTest02 {
public static void main(String[] args) {
//创建一个大小为8000000的数组,并且完成赋值
int[] arr = new int[8000000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 800000);
}
long start = System.currentTimeMillis();
//使用对应的基数排序
radixSort(arr);
long end = System.currentTimeMillis();
System.out.println("基数排序时间为:" + (end - start) + "ms");
}
public static void radixSort(int[] arr) {
//找到待排序的数组中的最大的数字
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i + 1];
}
}
//获取到最大数字有多少位
int maxLength = (max + "").length();
//定义一个二维数组:相当于一个含有是个10桶位置,且每个桶的大为数组的长度(防止出现极端情况)
int[][] bucket = new int[10][arr.length];
//记录每一个桶里面,实际存放的数据的个数(由于有10个桶,所以我们设置数组的大小为10)
//bucketElementCounts[0]=3 : 第一个桶位置的数据个数为3个
int[] bucketElementCounts = new int[10];
//循环的次数是由最大数字的位数决定的
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
for (int j = 0; j < arr.length; j++) {
//获取到我们待排序数组指定的位置上的指定位数
int digitOfElement = arr[j] / n % 10;
//将我们遍历到的数字,放到桶中的相应的位置
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//将我们之前放在桶中的数据,返回放到我们对应的数组中
int index = 0;
for (int k = 0; k < bucketElementCounts.length; k++) {
if (bucketElementCounts[k] != 0) {
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index] = bucket[k][l];
index++;
}
}
//每次成功转移数据后,需要清空我们桶中对应位置的数据
bucketElementCounts[k] = 0;
}
}
}
}