Arrays.sort(arr)是什麼排序

可樂加品客發表於2022-01-29

在學習過程中觀察到Arrays.sort(arr)演算法可以直接進行排序,但不清楚底層的程式碼邏輯是什麼樣子,記得自己之前在面試題裡面也有面試官問這個問題,只能說研究之後發現還是比較複雜的,並不是網上說的快排或者二分插入之類的。

首先看原始碼:

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

它呼叫了DualPivotQuicksort的sort方法,乍一看以為是快排,這是很多網上的博主的說法,繼續點開向下看(程式碼太長,沒耐心看可以直接跳過該段程式碼QWQ):

    static void sort(int[] a, int left, int right,
                     int[] work, int workBase, int workLen) {
        // Use Quicksort on small arrays
        if (right - left < QUICKSORT_THRESHOLD) {
            sort(a, left, right, true);
            return;
        }

        /*
         * Index run[i] is the start of i-th run
         * (ascending or descending sequence).
         */
        int[] run = new int[MAX_RUN_COUNT + 1];
        int count = 0; run[0] = left;

        // Check if the array is nearly sorted
        for (int k = left; k < right; run[count] = k) {
            if (a[k] < a[k + 1]) { // ascending
                while (++k <= right && a[k - 1] <= a[k]);
            } else if (a[k] > a[k + 1]) { // descending
                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;
                }
            } else { // equal
                for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
                    if (--m == 0) {
                        sort(a, left, right, true);
                        return;
                    }
                }
            }

            /*
             * The array is not highly structured,
             * use Quicksort instead of merge sort.
             */
            if (++count == MAX_RUN_COUNT) {
                sort(a, left, right, true);
                return;
            }
        }

        // Check special cases
        // Implementation note: variable "right" is increased by 1.
        if (run[count] == right++) { // The last run contains one element
            run[++count] = right;
        } else if (count == 1) { // The array is already sorted
            return;
        }

        // Determine alternation base for merge
        byte odd = 0;
        for (int n = 1; (n <<= 1) < count; odd ^= 1);

        // Use or create temporary array b for merging
        int[] b;                 // temp array; alternates with a
        int ao, bo;              // array offsets from 'left'
        int blen = right - left; // space needed for b
        if (work == null || workLen < blen || workBase + blen > work.length) {
            work = new int[blen];
            workBase = 0;
        }
        if (odd == 0) {
            System.arraycopy(a, left, work, workBase, blen);
            b = a;
            bo = 0;
            a = work;
            ao = workBase - left;
        } else {
            b = work;
            ao = 0;
            bo = workBase - left;
        }

        // Merging
        for (int last; count > 1; count = last) {
            for (int k = (last = 0) + 2; k <= count; k += 2) {
                int hi = run[k], mi = run[k - 1];
                for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
                    if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
                        b[i + bo] = a[p++ + ao];
                    } else {
                        b[i + bo] = a[q++ + ao];
                    }
                }
                run[++last] = hi;
            }
            if ((count & 1) != 0) {
                for (int i = right, lo = run[count - 1]; --i >= lo;
                    b[i + bo] = a[i + ao]
                );
                run[++last] = right;
            }
            int[] t = a; a = b; b = t;
            int o = ao; ao = bo; bo = o;
        }
    }

程式碼很長,簡要翻譯過來,這裡分了好幾種情況:


  1. 陣列長度小於286

    這裡又會呼叫一個sort方法,點開該sort(),又會劃分情況:

    • 陣列長度小於47,

      當leftmost(匯入的一個布林引數)為true,則使用直接插入排序;

      否則會呼叫另一種插入辦法,這裡可以觀察到一個註釋:

       /*
        * 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.
        */
      

      大致意思是:相鄰部分的每個元素都扮演著哨兵的角色,因此這允許我們避免在每次迭代中進行左範圍檢查。此外,我們使用了更優化的演算法,即所謂的成對插入排序它比插入排序的傳統實現更快(在快速排序的上下文中)。

      不過注意到,原函式引數傳參在這裡leftmost為true,所以一定是直接插入排序,以上作為了解。

    • 陣列長度大於47,採用一種快速排序的辦法,這裡因為程式碼太長,學藝不精,參考了一下白春雨大佬的文章分析:

      至於大過INSERTION_SORT_THRESHOLD(47)的,用一種快速排序的方法:

      1.從數列中挑出五個元素,稱為 “基準”(pivot);

      2.重新排序數列,所有元素比基準值的擺放在基準前面,所有元素比基準值的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽退出之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作;

      3.遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。


  2. 當陣列長度大於286時

    此時回到那段很長很長的程式碼段,在判斷小於286的長度陣列之後,從註解中:

    // Check if the array is nearly sorted
    

    這裡是指檢查陣列元素是不是快要排列好了,或者書面一點說,是不是有一定結構了,然後看後面的for迴圈,注意到一段程式碼:

                if (++count == MAX_RUN_COUNT) {
                    sort(a, left, right, true);
                    return;
                }
    

    這裡的sort和我們上面在陣列長度小於286時的那個sort方法是同一個方法,而針對這個count,是用來記錄逆序組的,打個比方:

    此時有一個陣列為1 5 6 9 8 7 2 3

    當陣列認定我們的順序應該為升序時,從第一個數開始數,此時9 8 7 2為降序,這就是逆序,將這四個陣列合成一個組稱為逆序組,然後再從3開始往後看。

    當統計到一個逆序組時,count++,所以可以看出,count是用來記逆序組的,那麼逆序組越多,這個結構就越混亂

    MAX_RUN_COUNT == 67 ,因此當count一直加到67時,就說明已經在一個混亂的臨界值了,此時執行sort()方法

    通過這一段分析,我們理一下思路:

    ​ 如果陣列能執行到這裡,說明陣列的長度大於等於286。符合該條件時,我們要判斷這個陣列是否有一定的結構:

    (1)count<67,說明不是那麼混亂,有一定結構,跳過;

    (2)count>=67,說明已經混亂了,沒有結構,執行sort方法,而已知陣列長度大於等於286,那麼必然大於47,一定執行快速排序

    跳過之後,經過程式碼的一大堆前置操作,最後看見下面的程式碼裡面一行註釋:

    //Merging
    

    顯然,這裡後面用到歸併排序了,不詳細贅述。


    好了,最後總結:

    1. 陣列長度小於286時,根據陣列長度,分兩種情況:
      • 陣列長度小於47,使用直接插入排序
      • 陣列長度大於47,使用快速排序
    2. 陣列長度大於286時,根據陣列排序情況,分兩種情況:
      • 陣列內順序較為混亂,即count逆序組數大於等於67,使用快速排序
      • 陣列內有一定順序,即count逆序組數小於67,使用歸併排序

    歡迎各位大佬討論

    參考資料:

    《Java的Arrays.sort()方法到底用的什麼排序演算法》 https://www.cnblogs.com/baichunyu/p/11935995.html 作者:白春雨

相關文章