本章内容前文 (不影响本章阅读)
数据结构与算法 ---- 详解堆排序、桶排序、基数排序及比较器内容(一)
1)比较器的实质就是重载比较运算符
2)比较器可以很好的应用在特殊标准的排序上
3)比较器可以很好的应用在特殊标准排序的结构上
我们都知道,java.util.Arrays下面提供了一种将指定的列表按从小到大排序的方法----sort()
拿参数为数组的sort举例
- /**
- * 将指定的数组按数字升序排序
- *
- * 参数:a - 要排序的数组
- */
- public static void sort(int[] a) {
- DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
- }
-
它的底层原理涉及到快速排序、归并排序、插入排序等等,这里就暂不追究了。它的作用是将一组数从小到大排序,例如:
- public static void main(String[] args) {
- int[] arr = {6,7,5,1,2,3,9};
- Arrays.sort(arr);
- System.out.println(Arrays.toString(arr));
- }
-
运行结果:[1, 2, 3, 5, 6, 7, 9]
到这里,如果你觉得sort()的用法仅此是升序排序,那就大大错了!
事实上,Arrays.sort()方法是有两个参数的
第二个参数就是确定数组顺序的比较器。如果指定了比较器,我们就可以根据该比较器的规则对某个对象进行排序。
好了,现在我有这样一个需求:我有一个Student对象,一共有三个属性,并给初始化值放到Student[]数组中,请问,我要如何实现按id升序(降序)呢?
- public static class Student{
- public String name; //姓名
- public int id; //id
- public int age; //年龄
-
- public Student(String name, int id, int age) {
- this.name = name;
- this.id = id;
- this.age = age;
- }
-
- public static void main(String[] args) {
- Student student1 = new Student("A",2,20);
- Student student2 = new Student("B",3,21);
- Student student3 = new Student("C",1,22);
- Student[] students = new Student[]{student1,student2,student3};
- }
-
答案是:实现Comparator接口,重写compare方法
首先要知道,比较器这样几个原则:
1)返回负数的时候,第一个参数排在前面
2)返回正数的时候,第二个参数排在前面
3)返回0的时候,谁在前面无所谓
也就是说,我们控制好返回值,就能决定排序顺序
- //按id升序
- public static class IdAscendingComparator implements java.util.Comparator<Student>{
- @Override
- public int compare(Student o1, Student o2) {
- return o1.id - o2.id; //2-3<0,升序
- }
- }
- //按id降序
- public static class IdDescendinComparator implements java.util.Comparator<Student>{
- @Override
- public int compare(Student o1, Student o2) {
- return o2.id - o1.id; //3-2>0,降序
- }
- }
-
测试如下
- public static void main(String[] args) {
- Student student1 = new Student("A",2,20);
- Student student2 = new Student("B",3,21);
- Student student3 = new Student("C",1,22);
-
- Student[] students = new Student[]{student1,student2,student3};
- //按照id升序比较
- System.out.println("按id升序");
- Arrays.sort(students,new IdAscendingComparator());
- printStudent(students);
- System.out.println("=============================");
- //按照id降序比较
- System.out.println("按id降序");
- Arrays.sort(students,new IdDescendinComparator());
- printStudent(students);
- }
- public static void printStudent(Student[] students){
- for (Student stu: students){
- System.out.println(stu);
- }
- }
-
这就是比较器的一个基本使用方式
这里补充一下上一篇堆排序的比较器的使用
数据结构与算法 ---- 详解堆排序、桶排序、基数排序及比较器内容(一)
- PriorityQueue<Integer> heap = new PriorityQueue<>()
-
java中PriorityQueue优先级队列是默认从小到大排序的,如果要实现一组数从大到小排序,那就要指定比较器,结合刚刚所知,代码如下
- public static class Acomp implements Comparator<Integer>{
- //如果返回负数,认为第一个参数应该放在上面
- //如果返回正数,认为第二个参数应该放在上面
- //如果返回0,认为谁放在上面都行
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2-o1;
- }
- }
-
- public static void main(String[] args) {
- PriorityQueue<Integer> heap = new PriorityQueue<>(new Acomp());
- heap.add(6);
- heap.add(9);
- heap.add(3);
- heap.add(2);
- heap.add(10);
- while (!heap.isEmpty()){
- System.out.println(heap.poll()); //大根堆
- }
- }
-
运行结果:10,9,6,3,2
Process finished with exit code 0
桶排序思想下的排序
1)计数排序
2)基数排序
分析:
1)桶排序思想下的排序都是不基于比较的排序
2)时间复杂度为O(N),额外空间复杂度O(M)
3)应用范围有限,需要样本的诗句状况能满足桶的划分
桶排序的原理其实十分简单,一组数,出桶入桶就完成了排序。为什么说他是不基于比较的排序呢?
假设某个班成绩数据A2,A4,A6,A1,A3…An(总之数据是无序的,A1<A2…且数据长度小于等于M),要将它们进行排序。那么我先准备一个大小为M的称为count的数组
count有M个单元(或称为桶),依次将数据放入指定桶内,初始为0。当每读入Ai的时候,对应count[Ai]增1(类似于一个计数器),然后依次出桶完成排序
这就是桶排序的原理,也是计数排序思想。它的时间复杂度为O(N),空间复杂度为O(M)。但是,这个算法也有缺陷,如果我需求的数据很大的话,那是不是意味着我的桶的数量也要非常大,这就很消耗空间内存。
对于“桶”概念有了一定的了解后,我们再来看看基数排序,这也是一个十分重要的排序。
像刚刚说的,如果给一个值域很大的一堆数,我要对其进行排序。显然不能用桶排序,那样需要准备太多桶了。
所以,基数排序的思想就从准备n个桶转变为进行n趟桶排序,利用从低位“数字”到高为“数字”不断的进桶出桶完成排序。什么意思呢?举个例子
我有一组初始元素
[051,026,160,234,445,202]
因为所有位上最大的数只有6,所以我准备7个桶(0~6),按照个位、十位、百位顺序依次进桶,如图所示:
这样三趟进出桶,就完成了排序,这里桶的实现是队列(先进先出)。
为什么按照这样的方式就能完成排序呢?
因为,基数排序可以看做是一个优先级排序(本人理解)。
拿“160”“234”来看,
1)按个位进出桶后,“234”排后面(因为它个位数大);
2)按十位进出桶后,“160”在后面
3)按百位进出桶后,“234”最终排在“160后面”
也就是说,个位、十位、百位的优先级是逐渐递增的,优先级越高,越往后走,所以最终只需要三趟就能完成排序。
- import java.util.Arrays;
-
- //基数排序
- public class RadixSort {
- public static void main(String[] args) {
- int[] arr = {4,6,9,7,2,5,3};
- radixSort(arr);
- }
- public static void radixSort(int[] arr){
- if (arr == null || arr.length<2){
- return;
- }
- System.out.println(Arrays.toString(radixSort(arr,0,arr.length-1,maxbits(arr))));
- //radixSort(arr,0,arr.length-1,maxbits(arr));
- }
- //在L。。R上排序;digit是这一批数字中最大的值有多少个十进制为
- public static int[] radixSort(int[] arr,int L,int R,int digit){
- final int radix = 10; //以10位基地
- int i = 0,j=0;
- //有多少个数准备多少个辅助空间
- int[] bucket = new int[R-L+1];
- for (int d = 1;d<=digit;d++){ //有多少位就进出几次
- //10个空间
- //count[0] 当前位(d位)是0的数字有多少个
- //count[1] 当前位(d位)是(0和1)的数字有多少个
- //count[2] 当前位(d位)是(0\1和2)的数字有多少个
- //count[i] 当前位(d位)是(0~i)的数字有多少个
- int[] count = new int[radix]; //count[0..9]
- for (i = L;i<=R;i++){
- j = getDigit(arr[i],d); //d是1取个位,d是2取十位......
- count[j]++; //计数器+1
- }
- for (i = 1 ; i<radix;i++){
- count[i] = count[i] + count[i-1]; //累加,看某位数比i位数值小的有多少个
- }
- //数组从右往左遍历
- for (i = R; i>=L;i--){
- j = getDigit(arr[i],d);
- bucket[count[j] - 1] =arr[i]; //从右往左把数放进新数组中
- count[j]--; //计数器-1
- }
- for (i=L,j=0;i<=R;i++,j++){
- arr[i] = bucket[j]; //把新数组中的数放回arr[i],再继续出桶入桶,直到有序
- }
- }
- return arr;
- }
- //最大值的位数
- public static int maxbits(int[] arr){
- int max = Integer.MIN_VALUE;
- //遍历过程中找到最大值
- for (int i = 0;i<arr.length;i++){
- max = Math.max(max,arr[i]);
- }
- //res用来表示最大值有多少十进制位
- int res = 0;
- while (max !=0){
- res++;
- max /= 10;
- }
- return res;
- }
- //取个位上的数
- public static int getDigit(int x,int d){
- return ((x/((int)Math.pow(10,d-1)))%10);
- }
- }
-
-
测试结果:[2, 3, 4, 5, 6, 7, 9]