Comparable和Comparator的应用实例及源码解析

应用场景:

        定义了Student类,学生有体重和身高两个属性,并且学生类实现了comparable接口,可以按体重升序排列,那么在Student类的使用中想对排序规则做出改变应该如何实现呢?

public class Student implements Comparable<Student>{
    int weight;
    int height;

    public Student(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

    //按体重升序
     @Override
    public int compareTo(Student o) {
        return this.weight - o.getWeight();
    }

    ...
    wieght 和 height 的get和set方法
    ...

    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student(40,140));
        list.add(new Student(45,125));
        list.add(new Student(42,135));
        list.add(new Student(43,115));
        list.add(new Student(41,130));
    //按体重升序排列
        Collections.sort(list);
        System.out.println(list);
    //按身高升序排列
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getHeight() - o2.getHeight();
            }
        });
        System.out.println(list);
    //按身高降序排列
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.getHeight() - o1.getHeight();
            }
        });
        System.out.println(list);
    }

//按体重升序排列:

[Student{weight=40, height=140}, Student{weight=41, height=130}, Student{weight=42, height=135}, Student{weight=43, height=115}, Student{weight=45, height=125}]

//按身高升序排列:
[Student{weight=43, height=115}, Student{weight=45, height=125}, Student{weight=41, height=130}, Student{weight=42, height=135}, Student{weight=40, height=140}]

身高降序排列:
[Student{weight=40, height=140}, Student{weight=42, height=135}, Student{weight=41, height=130}, Student{weight=45, height=125}, Student{weight=43, height=115}]                                                        


        如果要实现按体重降序,那么只要将compareTo方法的返回值修改为 o.getWeight() - this.weight 就能达到目的,那么影响排序规则的因素就是两个相减对象的顺序: 

o1.getHeight() - o2.getHeight() ———>按体重升序排列

o2.getHeight() - o1.getHeight() ———>按体重降序排列

那么这个返回值是如何影响排序规则的,接下来将从源码进入一探究竟 。


1.在返回值处设置断点调试

2. 进入了一个名为countRunAndMakeAscending的方法,Object[ ] a 内存放的是Student对象,对象数组下标范围为 [lo, hi),不包括下标hi,而runHi是用来标记集合中从lo开始最长的升序序列的末尾的后一位元素,也就是升序数组的范围是[lo, runHi),返回值(runHi - lo)是升序序列的长度。

        最后方法的返回值为2,因为集合中从lo(下标为0的元素)按体重升序的只有[40,45],45到42已经是降序, 所以集合中从最低位开始,按体重升序的不间断的升序序列只有前两个元素,返回2 。

 3. initRunLen接收了countRunAndMakeAscending方法的返回值,即对象数组内从lo开始的升序序列长度,将initRunLen的结果作为binarySort方法的输入参数,binarySort是二分插入排序。

4. 在直接插入排序过程中,待插入数据左边的序列总是有序的,针对有序序列,就可以用二分法去查找插入点,这就是二分插入排序,而countRunAndMakeAscending方法的作用就是统计对象数组左边有序序列的长度。

如果想了解二分插入排序更详细的步骤,建议查看

https://blog.csdn.net/qq_20222919/article/details/105217694?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166659886116782395324215%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166659886116782395324215&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-105217694-null-null.142^v59^pc_rank_34_1,201^v3^control_2&utm_term=%E4%BA%8C%E5%88%86%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F&spm=1018.2226.3001.4187icon-default.png?t=M85Bhttps://blog.csdn.net/qq_20222919/article/details/105217694?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166659886116782395324215%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166659886116782395324215&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-105217694-null-null.142^v59^pc_rank_34_1,201^v3^control_2&utm_term=%E4%BA%8C%E5%88%86%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F&spm=1018.2226.3001.4187

        binarySort的start参数是lo + initRunLen的结果,即对象数组a的有序序列部分为[lo, start),无序序列部分为[start, hi), binarySort执行的四个步骤:

        1)取出无序数组中的待插入元素 pivot  

        2)二分查找找到在有序数组中的插入位置

        3)将有序数组插入位置之后的元素后移

        4)插入pivot                                                                                                      

 private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
//1.取出无序数组中的待插入元素 pivot
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
//2.二分查找找到在有序数组中的插入位置 left
            while (left < right) {
                int mid = (left + right) >>> 1;
***问题的关键***
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            /*
             * The invariants still hold: pivot >= all in [lo, left) and
             * pivot < all in [left, start), so pivot belongs at left.  Note
             * that if there are elements equal to pivot, left points to the
             * first slot after them -- that's why this sort is stable.
             * Slide elements over to make room for pivot.
             */
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
//3.将有序数组插入位置之后的元素后移
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
//4.插入
            a[left] = pivot;
        }
    }

          当前 pivot 指向weight为42的Student对象,而最后找到的插入点left指向对象数组下标为1的Student对象,weight = 45      

         把下标为1的Student对象后移到下标为2的位置

         替换left指向位置的元素为 pivot,一轮排序完成,而后pivot = a[3], 继续查找插入完成全部排序

        那么排序的基本过程了解了之后,回到问题的关键,两个相减对象的顺序是如何影响排序结果的呢?

        在二分查找的过程中,pivot.compareTo(a[mid]) 的返回值是  this.weight - o.getWeight();   pivot 对应this,a[mid] 对应 o 。 就是比较 pivot 和 a[mid]的大小,如果 pivot 比 a[mid]小,即 pivot.compareTo(a[mid]) < 0  ,那么将执行 right  = mid ;缩小二分查找右边界的范围,得到新的mid值后继续比较,最后会将比中间值 ( a[mid] ) 小的元素插入到有序数组的前部分,这就形成了升序排列。

        如果交换了 this.weight - o.getWeight() 的相减顺序,变成 o.getWeight() - this.weight ,就等于     - ( this.weight - o.getWeight() ),那么pivot.compareTo(a[mid])得到的返回值就会变成相反的值,pivot - a[mid] < 0的返回值变成 > 0,那么将执行 left = mid + 1,缩小二分查找左边界的范围,最后查找到的待插入位置在有序数组的后半部分,把小于中间值的元素插入到有序数组的后半部分就是降序排列。

    while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
    }
//Student对象中重写的compareTo方法
     @Override
    public int compareTo(Student o) {
        return this.weight - o.getWeight();
    }

P.S:

        Comparator的compare方法返回值影响升序降序的原因相同, 同样是 pivot - a[mid] 的值大小

    while (left < right) {
       int mid = (left + right) >>> 1;
// c就是Comparator
       if (c.compare(pivot, a[mid]) < 0)
          right = mid;
       else
          left = mid + 1;
    }

    Collections.sort(list, new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getHeight() - o2.getHeight();
            }
    });

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值