剖析JDK8中Arrays.sort底層原理及其排序算法的選擇

寫這篇文章的初衷,是想寫篇Java和算法的實際應(yīng)用,讓算法不再玄乎,而Arrays.sort是很好的切入點,即分析Java的底層原理,又能學(xué)習(xí)里面的排序算法思想。希望能給在座各位在工作中或面試中一點幫助!轉(zhuǎn)載請注明出處:Michael孟良

點進sort方法:

      // Use Quicksort on small arrays
      if (right - left < QUICKSORT_THRESHOLD) {//QUICKSORT_THRESHOLD = 286
        sort(a, left, right, true);
        return;
      }

數(shù)組一進來,會碰到第一個閥值QUICKSORT_THRESHOLD(286),注解上說,小過這個閥值的進入Quicksort (快速排序),其實并不全是,點進去sort(a, left, right, true);方法:

// Use insertion sort on tiny arrays
    if (length < INSERTION_SORT_THRESHOLD) {
        if (leftmost) {
        ......

點進去后我們看到第二個閥值INSERTION_SORT_THRESHOLD(47),如果元素少于47這個閥值,就用插入排序,往下看確實如此:

            /*
             * 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;
元素少于47用插入排序

至于大過INSERTION_SORT_THRESHOLD(47)的,用一種快速排序的方法:
1.從數(shù)列中挑出五個元素,稱為 “基準”(pivot);
2.重新排序數(shù)列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的后面(相同的數(shù)可以到任一邊)。在這個分區(qū)退出之后,該基準就處于數(shù)列的中間位置。這個稱為分區(qū)(partition)操作;
3.遞歸地(recursive)把小于基準值元素的子數(shù)列和大于基準值元素的子數(shù)列排序。


快速排序(Quick Sort)

這是少于閥值QUICKSORT_THRESHOLD(286)的兩種情況,至于大于286的,它會進入歸并排序(Merge Sort),但在此之前,它有個小動作:

    // 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;
        }
    }

這里主要作用是看他數(shù)組具不具備結(jié)構(gòu):實際邏輯是分組排序,每降序為一個組,像1,9,8,7,6,8。9到6是降序,為一個組,然后把降序的一組排成升序:1,6,7,8,9,8。然后最后的8后面繼續(xù)往后面找。。。

每遇到這樣一個降序組,++count,當(dāng)count大于MAX_RUN_COUNT(67),被判斷為這個數(shù)組不具備結(jié)構(gòu)(也就是這數(shù)據(jù)時而升時而降),然后送給之前的sort(里面的快速排序)的方法(The array is not highly structured,use Quicksort instead of merge sort.)。

如果count少于MAX_RUN_COUNT(67)的,說明這個數(shù)組還有點結(jié)構(gòu),就繼續(xù)往下走下面的歸并排序:

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

從這里開始,正式進入歸并排序(Merge Sort)!

    // 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;
    }
歸并排序(Merge Sort)

總結(jié):
從上面分析,Arrays.sort并不是單一的排序,而是插入排序,快速排序,歸并排序三種排序的組合,為此我畫了個流程圖:


原創(chuàng)圖,轉(zhuǎn)發(fā)請標明出處

算法的選擇:
PS:關(guān)于排序算法的文章,推薦這兩篇,個人覺得寫得挺好,容易入門:
https://mp.weixin.qq.com/s/t0dsJeN397wO41pwBWPeTg
https://www.cnblogs.com/huangbw/p/7398418.html

穩(wěn)定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;

不穩(wěn)定:如果a原本在b的前面,而a=b,排序之后a可能會出現(xiàn)在b的后面;

時間復(fù)雜度按n越大算法越復(fù)雜來排的話:常數(shù)階O(1)、對數(shù)階O(logn)、線性階O(n)、線性對數(shù)階O(nlogn)、平方階O(n2)、立方階O(n3)、……k次方階O(n的k次方)、指數(shù)階O(2的n次方)。


轉(zhuǎn)圖

O(nlogn)只代表增長量級,同一個量級前面的常數(shù)也可以不一樣,不同數(shù)量下面的實際運算時間也可以不一樣。數(shù)量非常小的情況下(就像上面說到的,少于47的),插入排序等可能會比快速排序更快。
所以數(shù)組少于47的會進入插入排序。

快排數(shù)據(jù)越無序越快(加入隨機化后基本不會退化),平均常數(shù)最小,不需要額外空間,不穩(wěn)定排序。
歸排速度穩(wěn)定,常數(shù)比快排略大,需要額外空間,穩(wěn)定排序。
所以大于或等于47或少于286會進入快排,而在大于或等于286后,會有個小動作:“// Check if the array is nearly sorted”。這里第一個作用是先梳理一下數(shù)據(jù)方便后續(xù)的歸并排序,第二個作用就是即便大于286,但在降序組太多的時候(被判斷為沒有結(jié)構(gòu)的數(shù)據(jù),The array is not highly structured,use Quicksort instead of merge sort.),要轉(zhuǎn)回快速排序。


這就是jdk8中Arrays.sort的底層原理,自己在研究和分析中學(xué)到很多,希望能給各位工作中或面試中一些啟發(fā)和幫助!Thanks for watching!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 因為健忘,加上對各種排序算法理解不深刻,過段時間面對排序就蒙了。所以決定對我們常見的這幾種排序算法進行統(tǒng)一總...
    清風(fēng)之心閱讀 794評論 0 1
  • 排序算法 冒泡排序 選擇排序 插入排序 快速排序(最常見) 希爾排序 歸并排序 源碼:Sorting 冒泡排序 冒...
    廖少少閱讀 2,795評論 12 101
  • 排序算法說明 (1)排序的定義:對一序列對象根據(jù)某個關(guān)鍵字進行排序; 輸入:n個數(shù):a1,a2,a3,…,an 輸...
    code武閱讀 743評論 0 0
  • 文章出處:原畫人 《絕地求生》太火了。自推出...
    emotion者閱讀 1,184評論 0 0
  • 如果有一天 世界末日 彗星撞地球 我想我會第一時間去找你 接回家 和爸爸媽媽在一起 一家人吃一頓飯 身邊有小狗狗陪...
    我愛大白白閱讀 376評論 0 1

友情鏈接更多精彩內(nèi)容