一、简介
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
该算法具有稳定性。所谓稳定性,即在待排序序列中,存在多个相同的数,a = b 。
经过排序,这俩的相对次序保持不变,即a 仍然在b 的前面,这样的排序算法就具有稳定性。
二、原理
逐层从低位到高位(个位、十位…),把每个位数上的数据按升序排列。
三、例子:
将53,3,542,748,14,214按升序排序。
一共有10个桶(一维数组),下标0 ~ 9.
1、思路
将上面的数据放入arr数组中,并设置十个桶,下标0 ~ 9。第一轮排序时,依次取出这些数的个位上的数字(比如53的个位3,就放入下标为3的桶中),待所有数据的个位都提取并放入对应的桶之后,执行下一步:依次遍历所有桶,并取出桶中数据放入原来数组,完成后便开始第二轮排序;从更新后的arr数组中,提取十位数字,重复上列操作;第三轮亦同理。
第一轮排序:先取出个位上的数字,放入对应的桶中。
第二轮排序:同理,取出十位数,若缺位例如3,没有十位,则补零为03,再放入对应的桶中。
第三轮排序:同理,取出百位数,因为待排序数组最高位数是百位(748),所以只需要三轮排序。
这个最大位数怎么确定的呢?有个小技巧:
int max = arr[0];// 假设第一个数就是最大数
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int maxlength = (max + "").length();
2、分布推导:
package com.huey.sort;
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {
53, 3, 542, 748, 14, 214 };
radixSort(arr);
}
// 基数排序方法
public static void radixSort(int[] arr) {
// 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
// 1、二维数组包含10个一维数组
// 2、为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定义为arr.length
// 3、基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
// 为了记录每个桶中,实际存放了多少个数据,定义一个一维数组来记录各个桶的每次放入的数据个数
// 理解:
// 例如:bucketElementCounts[0],记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts = new int[10];
// ===============================================================
// 第1轮(针对每个元素的个位进行排序处理)
for (int j = 0; j < arr.length; j++) {
// 取出每个元素的个位的值
int digitOfElement = arr[j] % 10;
// 放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
// 例:53放在下标为3的桶,digitOfElement等于3,表示放在下标为3的桶中。
//bucketElementCounts[digitOfElement]表示第3个桶放入的数据个数。所以,整句话意思是:
//将arr[j](53)放到下标为3的桶中,这个下标为3的桶的第一个位置存放53.
//当结束了再把这个下标++,用来存放下个数据。
bucketElementCounts[digitOfElement]++;// 用来存放下一个
}
// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
// 遍历每一个桶,并将桶中的数据,放入原来数组
for (int k = 0; k < bucketElementCounts.length; k++) {
// 如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
// 循环该桶即第k个桶(即第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放入arr
arr[index++] = bucket[k][l];
}
}
// 第1轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
bucketElementCounts[k] = 0;
}
System.out.println("第1轮:" + Arrays.toString(arr));
// ===============================================================
// 第2轮(针对每个元素的十位进行排序处理)
for (int j = 0; j < arr.length; j++) {
// 取出每个元素的十位的值
int digitOfElement = arr[j] / 10 % 10;// 748/10 => 74%10 => 4
// 放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];// 例:53放在下标为3的桶
bucketElementCounts[digitOfElement]++;// 用来存放下一个
}
// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
// 遍历每一个桶,并将桶中的数据,放入原来数组
for (int k = 0; k < bucketElementCounts.length; k++) {
// 如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
// 循环该桶即第k个桶(即第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放入arr
arr[index++] = bucket[k][l];
}
}
// 第2轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
bucketElementCounts[k] = 0;
}
System.out.println("第2轮:" + Arrays.toString(arr));
// ===============================================================
// 第3轮(针对每个元素的百位进行排序处理)
for (int j = 0; j < arr.length; j++) {
// 取出每个元素的百位的值
int digitOfElement = arr[j] / 100 % 10;// 748/100 => 7%10 => 7
// 放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];// 例:53放在下标为3的桶
bucketElementCounts[digitOfElement]++;// 用来存放下一个
}
// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
// 遍历每一个桶,并将桶中的数据,放入原来数组
for (int k = 0; k < bucketElementCounts.length; k++) {
// 如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
// 循环该桶即第k个桶(即第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放入arr
arr[index++] = bucket[k][l];
}
}
}
System.out.println("第3轮:" + Arrays.toString(arr));
}
}
3、经推导,得到最终代码:
package com.huey.sort;
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {
53, 3, 542, 748, 14, 214 };
radixSort(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];
}
}
// 得到最大数是几位数【妙啊】
int maxlength = (max + "").length();
// 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
// 1、二维数组包含10个一维数组
// 2、为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定义为arr.length
// 3、基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
// 为了记录每个桶中,实际存放了多少个数据,定义一个一维数组来记录各个桶的每次放入的数据个数
// 理解:
// 例如:bucketElementCounts[0],记录的就是 bucket[0] 桶的放入数据个数
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];// 例:53放在下标为3的桶
bucketElementCounts[digitOfElement]++;// 用来存放下一个
}
// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
// 遍历每一个桶,并将桶中的数据,放入原来数组
for (int k = 0; k < bucketElementCounts.length; k++) {
// 如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
// 循环该桶即第k个桶(即第k个一维数组),放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放入arr
arr[index++] = bucket[k][l];
}
}
// 第i+1轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
bucketElementCounts[k] = 0;
}
System.out.println("第" + (i + 1) + "轮:" + Arrays.toString(arr));
}
}
}
输出结果:
速度测试:
8百万:0.5s左右
5千万:2 ~ 3s.
8千万,报错:堆溢出,不是电脑内存,得看你给 jvm 的堆分配了多少内存空间。
大约需要3GB。
80000000 * 10 * 4 / 1024 / 1024 / 1024 = 3GB