java JDK中Arrays.sort的排序算法分析

1、常见的排序算法

常见的排序算法有选择排序、插入排序、冒泡排序等基本的排序算法

高级一点的排序算法有快速排序算法,核心思想是从数组中挑选一个元素,使这个元素在正确的位置,比如左边的元素都不大于它,右边的元素都不小于它,一次递归,到最后子数组的大小为2或为1的时候,子数组有有序了,整个大数组就有序了。

归并排序算法,核心思想是将两个有序的数组的数组合并成一个数组。

希尔算法,整个算法比较特殊,就是算法的复杂度不是很确定。

详见 我的项目 https://gitee.com/dahaizhenqiang/algorithms-learn4.git

2.jdk中的算法

在jdk排序的时候,常见的就是Collections.sort() 和Arrays.sort()这两个静态方法,首先我们看Collections.sort(),一下jdk源码是jdk1.8

 @SuppressWarnings({"unchecked", "rawtypes"})
    public static  void sort(List list, Comparator c) {
        list.sort(c);
    }

Comparator接口是需要自己实现的,list中的元素根据compare()方法来排序,该方法调用的的是list.sort();继续跟进

 @SuppressWarnings({"unchecked", "rawtypes"})
    default void sort(Comparator c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

发现最终调用的也是Arrays.sort()方法,所以下面我们重点看Arrays.sort()方法,经断点调试,对Arraylist进行排序

会进入到一下的方法

 public static  void sort(T[] a, Comparator c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

 

其中传统的legcyMergeSort.userRequest的开关默认为false,我们看看legacyMergeRequest();

@SuppressWarnings({"rawtypes", "unchecked"})
    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; ilow && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);

        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

当需要排序的数组比较小的时候,直接采用Insertion sort on smallest arrays插入排序算法,下面的就是基于递归的归并算法了。

默认情况下调用的不是legacyMergeRequest(),而是调用 TimSort.sort(a, 0, a.length, c, null, 0, 0);代码如下

static  void sort(T[] a, int lo, int hi, Comparator c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        TimSort ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

当数组中的元素个数比较小的时候这里是个数小于32时,调用binarySort()方法,注意这个算法并不是二叉树推排序,

而是折半插入排序,

@SuppressWarnings("fallthrough")
    private static  void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator c) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            T pivot = 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).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, 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
            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);
            }
            a[left] = pivot;
        }
    }

设有一个序列a[0],a[1]...a[n];其中a[i-1]前是已经有序的,当插入时a[i]时,利用二分法搜索a[i]插入的位置,即插入的数组是有一个基本有序的数组。

而TimSort.sort()整体上为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果,总结就是合并算法的插入算法的结合。

更加详细的timSort排序算法的信息查看 https://blog.csdn.net/yangzhongblog/article/details/8184707

3.总结,算法很多,对于实际的程序应用情况,往往不会单纯的只用一种排序算法,而是多个排序算法的优化和结合,这就是算法知识的应用吧

 

 

 

你可能感兴趣的:(排序算法,排序算法)