• Java的Arrays.sort()方法到底用的什么排序算法


      暂时网上看过很多JDK8中Arrays.sort的底层原理,有些说是插入排序,有些说是归并排序,也有说大于域值用计数排序法,否则就使用插入排序。。。其实不全对。让我们分析个究竟:

    1 // Use Quicksort on small arrays
    2 if (right - left < QUICKSORT_THRESHOLD) 
    3 { 
    4        //QUICKSORT_THRESHOLD = 286
    5         sort(a, left, right, true);        
    6         return;
    7  }

      数组一进来,会碰到第一个阀值QUICKSORT_THRESHOLD(286),注解上说,小过这个阀值的进入Quicksort (快速排序),其实并不全是,点进去sort(a, left, right, true);方法:

    1 // Use insertion sort on tiny arrays
    2 if (length < INSERTION_SORT_THRESHOLD) 
    3 {        
    4     if (leftmost) 
    5     {
    6         ......

      点进去后我们看到第二个阀值INSERTION_SORT_THRESHOLD(47),如果元素少于47这个阀值,就用插入排序,往下看确实如此:

     1 /*
     2  * Traditional (without sentinel) insertion sort,
     3  * optimized for server VM, is used in case of
     4  * the leftmost part.
     5  */
     6 for (int i = left, j = i; i < right; j = ++i) 
     7 {               
     8      int ai = a[i + 1];               
     9      while (ai < a[j]) 
    10      {
    11           a[j + 1] = a[j];                   
    12           if (j-- == left) 
    13           {       
    14                break;
    15            }
    16       }
    17       a[j + 1] = ai;

    元素少于47用插入排序

      至于大过INSERTION_SORT_THRESHOLD(47)的,用一种快速排序的方法:

      1.从数列中挑出五个元素,称为 “基准”(pivot);

      2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

      3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

    快速排序(Quick Sort)  

      这是少于阀值QUICKSORT_THRESHOLD(286)的两种情况,至于大于286的,它会进入归并排序(Merge Sort),但在此之前,它有个小动作:

     1 // Check if the array is nearly sorted
     2     for (int k = left; k < right; run[count] = k) {        if (a[k] < a[k + 1]) { // ascending
     3             while (++k <= right && a[k - 1] <= a[k]);
     4         } else if (a[k] > a[k + 1]) { // descending
     5             while (++k <= right && a[k - 1] >= a[k]);            for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {                int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
     6             }
     7         } else { // equal
     8             for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {                if (--m == 0) {
     9                     sort(a, left, right, true);                    return;
    10                 }
    11             }
    12         }        /*
    13          * The array is not highly structured,
    14          * use Quicksort instead of merge sort.
    15          */
    16         if (++count == MAX_RUN_COUNT) {
    17             sort(a, left, right, true);            return;
    18         }
    19     }

      这里主要作用是看他数组具不具备结构:实际逻辑是分组排序,每降序为一个组,像1,9,8,7,6,8。9到6是降序,为一个组,然后把降序的一组排成升序:1,6,7,8,9,8。然后最后的8后面继续往后面找。

      每遇到这样一个降序组,++count,当count大于MAX_RUN_COUNT(67),被判断为这个数组不具备结构(也就是这数据时而升时而降),然后送给之前的sort(里面的快速排序)的方法(The array is not highly structured,use Quicksort instead of merge sort.)

      如果count少于MAX_RUN_COUNT(67)的,说明这个数组还有点结构,就继续往下走下面的归并排序。

    总结:

      从上面分析,Arrays.sort并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合,为此我画了个流程图:

       O(nlogn)只代表增长量级,同一个量级前面的常数也可以不一样,不同数量下面的实际运算时间也可以不一样。

      数量非常小的情况下(就像上面说到的,少于47的),插入排序等可能会比快速排序更快。 所以数组少于47的会进入插入排序。  

      快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。

      归排速度稳定,常数比快排略大,需要额外空间,稳定排序。

      所以大于或等于47或少于286会进入快排,而在大于或等于286后,会有个小动作:“// Check if the array is nearly sorted”。这里第一个作用是先梳理一下数据方便后续的归并排序,第二个作用就是即便大于286,但在降序组太多的时候(被判断为没有结构的数据,The array is not highly structured,use Quicksort instead of merge sort.),要转回快速排序。

  • 相关阅读:
    如何理解c和c++ 的复杂类型声明
    xp自带扫雷bug
    求三角形的外接圆
    判断一个点是否在一个三角形内
    三角函数角度公式
    弗洛伊德(Floyd)算法
    在Win32应用程序中用Console控制台输出信息
    无法打开libcp.lib
    C#获取当前应用程序所在路径及环境变量
    C#事件的发送方和接收方(订阅方)【转】
  • 原文地址:https://www.cnblogs.com/baichunyu/p/11935995.html
Copyright © 2020-2023  润新知