Arrays.sort 和 Collections.sort 实现原理

Arrays.sort

基础知识

DualPivotQuicksort

那么Dual-Pivot快排到底是怎么样的一个排序算法呢?其实从它的名字里面可以看出一些端倪:在经典快排里面有一个pivot的概念,它是用来分隔大数和小数的 -- 这个pivot把一个数组分成两份。那么所谓的Dual-Pivot其实就是用两个Pivot, 把整个数组分成三份。

timeSort

1.扫描数组,确定其中的单调上升段和严格单调下降段,将严格下降段反转。我们将这样的段称之为run。
2.定义最小run长度,短于此的run通过插入排序合并为长度高于最小run长度;
3.反复归并一些相邻run,过程中需要避免归并长度相差很大的run,直至整个排序完成;
4.如何避免归并长度相差很大run呢, 依次将run压入栈中,若栈顶run X,run Y,run Z 的长度违反了X>Y+Z 或 Y>Z 则Y run与较小长度的run合并,并再次放入栈中。 依据这个法则,能够尽量使得大小相同的run合并,以提高性能。注意Timsort是稳定排序故只有相邻的run才能归并。

1.基本类型

  public static void sort(char[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
  public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
........

我们可以看到Arrays.sort是一个多态的方法,每一个基本类型都有该方法,并且其都调用的是优化后的快速排序。
下面我们以int[]型为例

DualPivotQuicksort是JDK1.7开始的采用的快速排序算法。
一般的快速排序采用一个枢轴来把一个数组划分成两半,然后递归之。
大量经验数据表面,采用两个枢轴来划分成3份的算法更高效,这就是DualPivotQuicksort。

DualPivotQuicksort,主要做了以下几个方面的优化:

  1. 当待排序的数组中的元素个数较少时,源码中的阀值为47,采用的是插入排序。尽管插入排序的时间复杂度为0(2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。
  2. 当待排序的数组个数小于286时,直接使用双枢轴快速排序。
    (pivot的选取方式是将数组分成近视等长的七段,而这七段其实是被5个元素分开的,会查看5个元素查看是否有相同,如果有相同。代表重复比较多。会使用单枢轴快速排序)
    注意:
    如果不相同,将5个元素从小到大排序,取出第2个和第4个,分别作为pivot1和pivot2。
    这是个改进的单枢轴快速排序。这个时候也是3个指针的算法。因为,这个算法就像是分成3类,一类小于枢轴,一类大于枢轴,一类等于枢轴。只用对左右两种进行递归
  3. 大于286时,先使用TimSort的思想,找出正序或者倒序的数(run的栈),然后合并各个run,最后完成TimSort。、

2.对象类型

文档写的是:
可以使用系统属性选择旧的归并排序实现(为了与损坏的比较器兼容)。由于循环依赖关系,在封闭类中不能是静态布尔值。将在未来的版本中删除。

 public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

1.7之前

来看看 mergeSort 的具体实现。

  1. 当 array.length < 7 时会使用插入排序(insertion sort)
  2. 其他使用二路归并。

1.7之后

在jdk1.7之后。Arrays类中的sort方法有一个分支推断,当LegacyMergeSort.userRequested为true的情况下,採用legacyMergeSort,否则採用ComparableTimSort。而且在legacyMergeSort的凝视上标明了该方法会在以后的jdk版本号中废弃,因此以后Arrays类中的sort方法将採用ComparableTimSort类中的sort方法。

  1. 数组大小<32情况下,采用“mini-TimeSort”,实质是二分排序。
  2. 大于32的时候计算run的最小长度minrun(即开启合并的最小长度)返回的数要么小于16,要么是16,要么介于[16, 32]之间
  3. 把小于minrun的插入,大于minrun合并(当分区大于一的时候,如果分区大于三,如果后两个分区的和大于前一个分区,则中间的分区与最小的分区先合并,否则合并后两个分区)。

Collections.sort

其实就是调用timesort

 @Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
posted @ 2022-05-17 17:20  度一川  阅读(252)  评论(0)    收藏  举报