美文网首页
Arrays中sort使用的排序手段

Arrays中sort使用的排序手段

作者: Minority | 来源:发表于2020-03-23 09:57 被阅读0次

转载:解析Arrays中sort方法的黑科技

排序问题是算法里面的经典问题,也是计算机学科数据结构课程里面的必修课,面对诸多的如插入排序,快速排序,堆排序,归并排序等等经典排序算法,JDK的实现者是如何选择排序算法的呢?我们经常使用的对数据进行排序的算法Arrays.sort,Collections.sort方法,那么具体它们是如何实现的呢,本文尝试从jdk 1.8的实现源码上进行分析,学习在实际工业环境下对排序算法的使用方法。

概述

Collections的sort方法的实现最终调用了Arrays的sort方法,所以我们仅仅分析Arrays中的sort方法,这一节,我们尝试从宏观的角度来看Arrays的sort方法。Arrays的sort方法分为两种:基本类型和Object类型。他们的实现方法不同

基本类型:

我们从Arrays.sort(int[])方法来概述基本类型排序的基本思路:

    1. 如果数组元素个数小于NSERTION_SORT_THRESHOLD(47),那么使用改进的插入排序进行排序
    1. 如果元素个数大于插入排序的阈值并且小于快速排序的阈值QUICKSORT_THRESHOLD(286),则使用改进的双轴快速排序进行排序
    1. 如果元素个数大于快速排序的阈值,根据数组的无序程度来判定继续使用哪种算法,无序程度通过将数组划分为不同的有序序列的个数来判定,如果有序序列的个数大于MAX_RUN_COUNT(67),则认为原数组基本无序,则仍然使用双轴快速排序,如果小于MAX_RUN_COUNT,则认为原数组基本有序,使用归并排序进行排序。

Object对象类型:

我们从Arrays.sort(Object[])的实现来概述Object对象类型的排序,其基本思路是:

    1. 如果数组元素个数小于MIN_MERGE(32),那么就会调用二分插入排序binarySort方法进行排序,所谓二分排序,是指在插入的过程中,使用二分查找的方法查找待插入的位置,这种查找方法会比线性查找快一点。
    1. 如果大于MIN_MERGE,则将数组划分成多个有序块进行归并排序

综上所述,无论是基本类型的排序还是Object类型的排序,都使用了多种排序的组合,这种组合的原因在于在不同的排序规模下,需要适应性的选用不同的排序方法,例如,在排序规模较小的情况下,复杂度为O(n^2)的插入排序能获得比例如快速排序更好的排序性能,而在数组基本有序的情况下,归并排序比快速排序相比是更好的选择。总的来说,根据不同的数组规模决定使用不同的排序方法。接下来我们通过详细的代码分析其具体实现。


基本类型排序(Arrays.sort(int[]))

Arrays的sort方法直接调用了DualQivotQuicksort的sort方法,类名的英语意思是双轴快速排序。可见双轴快速排序是关键。



接下来进入sort方法,通过英语注释我们可以看到第一步,如果数组长度较小的话(小于QUICKSORT_THRESHOLD),则使用sort方法进行排序,执行完方法结束。



那么sort方法又是如何排序的,判断数组长度是否小于INSERTION_SORT_THRESHOLD,如果是的话,则使用插入排序,并且在插入排序使用leftmost控制使用传统的插入排序还是改进的插入排序。改进的插入排序被称为成对插入排序(pair insertion sort),其基本思想是一次取出两个未排序数组的元素,并且确保(a1>a2),那么在a1找到位置后,a2的位置肯定在a1之前,继续查找即可。
if (length < INSERTION_SORT_THRESHOLD) {
      if (leftmost) {//默认情况下,经典插入排序
          /*
           * Traditional (without sentinel) insertion sort,
           * optimized for server VM, is used in case of
           * the leftmost part.
           */
          for (int i = left, j = i; i < right; j = ++i) {
              int ai = a[i + 1];
              while (ai < a[j]) {
                  a[j + 1] = a[j];
                  if (j-- == left) {
                      break;
                  }
              }
              a[j + 1] = ai;
          }
      } else {//否则,使用改进后的成对插入排序
          /*
           * Skip the longest ascending sequence.
           */
          do {
              if (left >= right) {
                  return;
              }
          } while (a[++left] >= a[left - 1]);
          /*
           * Every element from adjoining part plays the role
           * of sentinel, therefore this allows us to avoid the
           * left range check on each iteration. Moreover, we use
           * the more optimized algorithm, so called pair insertion
           * sort, which is faster (in the context of Quicksort)
           * than traditional implementation of insertion sort.
           */
          for (int k = left; ++left <= right; k = ++left) {
              int a1 = a[k], a2 = a[left];
              if (a1 < a2) {
                  a2 = a1; a1 = a[left];
              }
              while (a1 < a[--k]) {
                  a[k + 2] = a[k];
              }
              a[++k + 1] = a1;
              while (a2 < a[--k]) {
                  a[k + 1] = a[k];
              }
              a[k + 1] = a2;
          }
          int last = a[right];
          while (last < a[--right]) {
              a[right + 1] = a[right];
          }
          a[right + 1] = last;
      }
      return;
  }

如果不是小于INSERTION_SORT_THRESHOLD,则使用5分位法找出5个位置的值做双轴快速排序


这里有必要解释一下双轴快速排序,双轴快速排序的思想是相对于单轴快速排序,经典的单轴快速排序,每次选择一个元素作为轴值pivot,然后使用双指针将小于pivot移到pivot的左边,大于pivot的值移到右边,使得pivot处于最终位置,这个过程称为一次排序,对两边递归的调用一次排序可以得到最终的排序结果。
而双轴快速排序的基本思想是一次可以将两个元素放到最终位置上,假设这两个轴值为pivot1,pivot2,那么一次排序后,最终数组被这两个元素划分成三块:<pivot1, >=pivot1并且<=pivot2, >pivot2。为了达到这种划分,我们需要三个指针来进行操作i,k,j,i左边的是小于pivot1的,j右边的是大于pivot1的,k用来扫描,当k和j相聚的时候,则扫描结束。关于双轴快速排序的具体实现可以参考这篇文章《单轴快排(SinglePivotQuickSort)和双轴快排(DualPivotQuickSort)及其JAVA实现》这篇文章,写的很好。
相比单轴快速排序,双轴快速排序能够获取更快的排序效率,我们来看一看源码中的关键实现,从源码上来看,当5分位点所有值都不相同的时候,选取第二个点和第四个点作为双轴进行双轴快速排序。

否则的话,就使用第三个点进行单轴快速排序



以上是数组长度小于QUICKSORT_THRESHOLD(286)时候的排序策略,如果大于这个快速排序的阈值,又是怎么做的呢,我们继续往下看源码,发现这么一段代码,其目的也很明确,就是检查原来数组是不是基本有序,方法是找出有序的数组片段,用count进行统计,如果count一旦等于MAX_RUN_COUNT,则认为基本无序,使用我们上面提到的方法进行快速排序。


基本无序状态检查



在上面统计count的过程中,同样使用run数组记录所有的基本有序的数组的最后一个元素的下标,如果判定结果是基本有序,则使用的归并排序进行最终的排序,方法是通过run方法记录的下标每次合并两个有序序列,最终使得数组有序,这里就不贴出很长的归并代码。
基本上,基础类型的排序算法就这样讲完了。

Object类型排序(Arrays.sort(Object[]))

Arrays.sort方法的Object数组要求实现Comparable接口,因为在其实现过程中有
Object类型的排序中legacyMergeSort是经典的归并排序,不过它即将被废弃了,这里只是用来兼容老的排序方法,现在默认使用的是ComparableSort里面的sort方法,我们的分析着重在这个方法。



首先,如果数组的长度小于MIN_MERGE(32),那么就会调用二分插入排序binarySort方法进行排序,所谓二分排序,是指在插入的过程中,使用二分查找的方法查找待插入的位置,这种查找方法会比线性查找快一点。



如果不答应MIN_MERGE,则使用归并排序,归并的方法是将数组换分成等块的小数组(除最后一块),在小数组内进行二分插入排序,排序后将当前数组的初始位置,长度使用pushRun方法压栈,mergeCollapse方法每次会从数组中取出两个小数组进行归并排序,最终循环结束后,数组也已经排序完成。

嗯,基本上就是这样了。

参考文献

相关文章

  • Arrays中sort使用的排序手段

    转载:解析Arrays中sort方法的黑科技 排序问题是算法里面的经典问题,也是计算机学科数据结构课程里面的必修课...

  • 对象数组如何排序-Comparable接口详解

    1 普通数组使用Arrays.sort方法排序 在Arrays工具类中,sort函数可以对普通数组进行排序,如以下...

  • Arrays.sort()与Collections.sort()

    一、Arrays.sort() 应用:对数组进行排序 默认排序:升序排列 1.Arrays.sort()使用方法 ...

  • JAVA 集合框架(三)排序

    对Array的排序 同过Arrays的sort方法。 基本类型的排序 调用Arrays.sort(基本类型数组);...

  • Arrays.sort使用的排序算法

    直接开门见山 java中Arrays.sort使用了两种排序方法,快速排序和优化的归并排序。 快速排序主要是对哪些...

  • Arrays.sort()排序算法分析

    Arrays.sort()根据入参类型选择以下排序算法 基本类型数组使用快速排序 对象数组使用归并排序 原因 使用...

  • Comparable接口

    使用Comparable接口进行自定义排序 集合:Collections.sort() 数组:Arrays.sor...

  • 算法排序-归并排序

    Java系统提供的Arrays.sort函数。对于基础类型,底层使用快速排序。对于非基础类型,底层使用归并排序。请...

  • java常用代码之二

    1.排序 数组:Arrays.sort(strings); List:Collections.sort(list)...

  • 排序问题

    数组排序 数组排序最简单了,直接Arrays.sort(a); a是待排序的数组 根据对象中的成员变量来排序 这个...

网友评论

      本文标题:Arrays中sort使用的排序手段

      本文链接:https://www.haomeiwen.com/subject/gzyiyhtx.html