基数排序
基数排序是一种基于分配的排序(空间换时间),不同于常见的基于比较的排序(冒泡、快排、归并…等)。
基于比较的排序时间复杂度通常是O(n^2)或者O(nlogn),下限是O(nlogn);
基于分配的排序算法的时间复杂度可以达到O(n),但需要消耗额外空间;
在某些时候,基数排序的效率高于其它基于比较的排序算法(快排、归并等)。
原理
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。进行了c次(c是整数的位数)比较之后,得到一个有序的数组。
基数排序是桶排序的扩展,每一次排序建立在桶排序的基础上。
步骤
1. 将每个数字统一位数长度(数位短的前面补0);
2. 从最低位(个位)开始,依次进行每次排序;
3. 最高位排序完成后,数组就变成了有序数组。
实例分析
通过基数排序对一个无序数组进行排序{53, 542, 3, 63, 14, 214, 154, 748, 616},它的示意图如下:
比较过程
结合上图分析比较过程。
每次比较,根据相应位数上的值,将元素放入对应的桶(0-10)中。
第一次比较 “按个位排序”
桶编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
数字 | 542 | 053 | 014 | 616 | 748 | |||||
数字 | 003 | 214 | ||||||||
数字 | 063 | 154 |
将这些数字按新的顺序放入原数组,如图一,准备第二次排序
第二次比较 “按十位比较”
桶编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
数字 | 003 | 014 | 542 | 053 | 063 | |||||
数字 | 214 | 748 | 154 | |||||||
数字 | 616 |
同样,将这些数字按新的顺序放入原数组,如图一,准备第三次排序
第三次比较 “按百位比较”
桶编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
数字 | 003 | 154 | 214 | 542 | 616 | 748 | ||||
数字 | 014 | |||||||||
数字 | 053 | |||||||||
数字 | 063 |
经过第三次排序,如图一,得到一个有序的数组。
实现
计算排序的次数
排序的次数即数组统一的位数(也是最大元素的位数),在这里是 3 位,总共需要比较3次。
//获取最大值
int max = a[0];
for (int i = 0; i < a.length; i++) {
if (max < a[i]) {
max = a[i];
}
}
//根据最大值确定排序的遍数(如369三位数,就是3遍)
int time = 0;
while (max > 0) {
max = max / 10;
time ++;
}
排序过程
每一次排序之后,都要根据排序的结果记录新的顺序,以便下一次排序。
思路如下:
1. 将桶数组看成是一个只有10个元素(0-9)的队列,建立该队列(父队列);
2. 父队列中的每一个元素都代表一个子队列,用于存放每个桶中的元素,可能0个也可能多个;
注:采用数组ArrayList来代替子队列,也可以采用链表
//建立一个主队列,包含10个子队列
List<ArrayList> queue = new ArrayList<ArrayList>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> subQueue = new ArrayList<>();
queue.add(subQueue);
}
3. 分配数组元素
将每一个数组元素分配进入相应的桶中,即子队列中。
每次分配时,i 的值表示获取元素对应位数上的数字:
i = 0 ,表示个位;
i= 1,表示十位;
i = 2, 表示百位;
//分配数组元素
for (int j = 0; j < a.length; j++) {
int c = a[j] % (int)Math.pow(10, i + 1); //当前位上的数字
int x = c / (int)Math.pow(10, i); //子队列的序号
//System.out.println(x);
ArrayList<Integer> subQueue = queue.get(x); //从父队列获取子队列
subQueue.add(a[j]);
queue.set(x, subQueue);
}
代码实现
public static void radisSort(int[] a) {
//获取最大值
int max = a[0];
for (int i = 0; i < a.length; i++) {
if (max < a[i]) {
max = a[i];
}
}
//根据最大值确定排序的遍数(如369三位数,3遍)
int time = 0;
while (max > 0) {
max = max / 10;
time ++;
}
//建立一个主队列,包含10个子队列
List<ArrayList> queue = new ArrayList<ArrayList>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> subQueue = new ArrayList<>();
queue.add(subQueue);
}
//time次排序
for (int i = 0; i < time; i++) {
//分配数组元素
for (int j = 0; j < a.length; j++) {
int c = a[j] % (int)Math.pow(10, i + 1); //当前位上的数字
int x = c / (int)Math.pow(10, i); //子队列的序号
//System.out.println(x);
ArrayList<Integer> subQueue = queue.get(x); //从父队列获取子队列
subQueue.add(a[j]);
queue.set(x, subQueue);
}
//分配结束后,记录新的顺序
int k = 0;
for (int n = 0; n < 10; n++) {
//取出有元素的子队列
while (queue.get(n).size() > 0) {
ArrayList<Integer> subQueue = queue.get(n);
a[k ++] = subQueue.get(0); //头部元素
subQueue.remove(0); //移除元素
}
}
//打印每次排序后的新数组
// for (int m : a) {
// System.out.print(m + " ");
// }
}
//打印最终排好序的数组
for (int m : a) {
System.out.print(m + " ");
}
}
public static void main(String[] args) {
int[] a = {53, 542, 3, 63, 14, 214, 154, 748, 616};
radisSort(a);
}
输出:
3 14 53 63 154 214 542 616 748
以上,就是基数排序的大致过程。