jdk8 Arrays.sort()实现分析

Arrays.sort()根据所需要排序的数组的特点会选择不同的排序算法。

 

第一种情况,当被排序的数组长度小于47并且排序是从数组起始位置开始的时候,采用的排序方式是最简单的插入排序。

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但排序不是从数组起始位置开始的时候,那么就会选择成对插入排序的方式进行排序。

 do {
        if (left >= right) {
            return;
        }
    } while (a[++left] >= a[left - 1]);

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

成对插入排序的方式在插入排序的基础上同时对两个元素进行插入排序,在之前的基础上第二个也就是较小的数可以在较大数的基础上进行插入,减少了比较次数。

 

第三种情况和第四种排序算法的选择发生在当数组元素个数大于47小于286的时候。

首先,会对数组相应的进行切分。在这里的切分方式选择取数组中点,前后七分之一的方式相应得到五点,并对这五点进行排序,类似希尔排序。

int e3 = (left + right) >>> 1; // The midpoint
int e2 = e3 - seventh;
int e1 = e2 - seventh;
int e4 = e3 + seventh;
int e5 = e4 + seventh;
// Sort these elements using insertion sort
if (a[e2] < a[e1]) { int t = a[e2]; a[e2] = a[e1]; a[e1] = t; }

if (a[e3] < a[e2]) { int t = a[e3]; a[e3] = a[e2]; a[e2] = t;
    if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
}
if (a[e4] < a[e3]) { int t = a[e4]; a[e4] = a[e3]; a[e3] = t;
    if (t < a[e2]) { a[e3] = a[e2]; a[e2] = t;
        if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
    }
}
if (a[e5] < a[e4]) { int t = a[e5]; a[e5] = a[e4]; a[e4] = t;
    if (t < a[e3]) { a[e4] = a[e3]; a[e3] = t;
        if (t < a[e2]) { a[e3] = a[e2]; a[e2] = t;
            if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
        }
    }
}

算法的选择发生在此处,如果选择到的五个切分点不存在相同的,那么将会采用双轴快速排序的方式进行排序。

outer:
for (int k = less - 1; ++k <= great; ) {
    int ak = a[k];
    if (ak < pivot1) { // Move a[k] to left part
        a[k] = a[less];
        /*
         * Here and below we use "a[i] = b; i++;" instead
         * of "a[i++] = b;" due to performance issue.
         */
        a[less] = ak;
        ++less;
    } else if (ak > pivot2) { // Move a[k] to right part
        while (a[great] > pivot2) {
            if (great-- == k) {
                break outer;
            }
        }
        if (a[great] < pivot1) { // a[great] <= pivot2
            a[k] = a[less];
            a[less] = a[great];
            ++less;
        } else { // pivot1 <= a[great] <= pivot2
            a[k] = a[great];
        }
        /*
         * Here and below we use "a[i] = b; i--;" instead
         * of "a[i--] = b;" due to performance issue.
         */
        a[great] = ak;
        --great;
    }
}

// Swap pivots into their final positions
a[left]  = a[less  - 1]; a[less  - 1] = pivot1;
a[right] = a[great + 1]; a[great + 1] = pivot2;

// Sort left and right parts recursively, excluding known pivots
sort(a, left, less - 2, leftmost);
sort(a, great + 2, right, false);

双轴快排相较于普通快排,多了一个比对对象,将数组划分为了三个区间,具体如下。

/*
 * Partitioning:
 *
 *   left part           center part                   right part
 * +--------------------------------------------------------------+
 * |  < pivot1  |  pivot1 <= && <= pivot2  |    ?    |  > pivot2  |
 * +--------------------------------------------------------------+
 *               ^                          ^       ^
 *               |                          |       |
 *              less                        k     great
 *
 * Invariants:
 *
 *              all in (left, less)   < pivot1
 *    pivot1 <= all in [less, k)     <= pivot2
 *              all in (great, right) > pivot2
 *
 * Pointer k is the first index of ?-part.
 */

一轮双轴快排结束将会被分为小于基准1,大于基准1小于基准2,大于基准2三个分区,之后继续对这三个分区继续分别排序。

其中,如果中间的部分过于庞大,还存在着优化方案,当选择的两个基准中的插值过大的时候,中间部分也许会过于庞大(超过整个排序部分的七分之四),将会针对这一区间内的重复键进行优化。

outer:
for (int k = less - 1; ++k <= great; ) {
    int ak = a[k];
    if (ak == pivot1) { // Move a[k] to left part
        a[k] = a[less];
        a[less] = ak;
        ++less;
    } else if (ak == pivot2) { // Move a[k] to right part
        while (a[great] == pivot2) {
            if (great-- == k) {
                break outer;
            }
        }
        if (a[great] == pivot1) { // a[great] < pivot2
            a[k] = a[less];
            /*
             * Even though a[great] equals to pivot1, the
             * assignment a[less] = pivot1 may be incorrect,
             * if a[great] and pivot1 are floating-point zeros
             * of different signs. Therefore in float and
             * double sorting methods we have to use more
             * accurate assignment a[less] = a[great].
             */
            a[less] = pivot1;
            ++less;
        } else { // pivot1 < a[great] < pivot2
            a[k] = a[great];
        }
        a[great] = ak;
        --great;
    }
}

在这里中间部分会继续被分为三个部分,如下图。

/*
 * Partitioning:
 *
 *   left part         center part                  right part
 * +----------------------------------------------------------+
 * | == pivot1 |  pivot1 < && < pivot2  |    ?    | == pivot2 |
 * +----------------------------------------------------------+
 *              ^                        ^       ^
 *              |                        |       |
 *             less                      k     great
 *
 * Invariants:
 *
 *              all in (*,  less) == pivot1
 *     pivot1 < all in [less,  k)  < pivot2
 *              all in (great, *) == pivot2
 *
 * Pointer k is the first index of ?-part.
 */

其中与基准1和基准2 相同的键将会被分到前后两个分区,并会被从接下来要继续递归排序的中间分区中剔除,尽可能减少了中间分区的大小。

 

 

相较于第三种情况,第四种情况排序算法的选择在之前选择完毕五个基准点之后,如果任意两个点存在相同的情况,选择更合适存在大量相同键的三项切分快速排序。

for (int k = less; k <= great; ++k) {
    if (a[k] == pivot) {
        continue;
    }
    int ak = a[k];
    if (ak < pivot) { // Move a[k] to left part
        a[k] = a[less];
        a[less] = ak;
        ++less;
    } else { // a[k] > pivot - Move a[k] to right part
        while (a[great] > pivot) {
            --great;
        }
        if (a[great] < pivot) { // a[great] <= pivot
            a[k] = a[less];
            a[less] = a[great];
            ++less;
        } else { // a[great] == pivot
            /*
             * Even though a[great] equals to pivot, the
             * assignment a[k] = pivot may be incorrect,
             * if a[great] and pivot are floating-point
             * zeros of different signs. Therefore in float
             * and double sorting methods we have to use
             * more accurate assignment a[k] = a[great].
             */
            a[k] = pivot;
        }
        a[great] = ak;
        --great;
    }
}

相比双轴快排,三向切分快排只选取了一个基准点来对数组进行切分,而相比普通快排,在大于基准点和小于基准点两个分区的基础上,增加了与基准点相同的分区这一中间分区。

/*
 * Partitioning degenerates to the traditional 3-way
 * (or "Dutch National Flag") schema:
 *
 *   left part    center part              right part
 * +-------------------------------------------------+
 * |  < pivot  |   == pivot   |     ?    |  > pivot  |
 * +-------------------------------------------------+
 *              ^              ^        ^
 *              |              |        |
 *             less            k      great
 *
 * Invariants:
 *
 *   all in (left, less)   < pivot
 *   all in [less, k)     == pivot
 *   all in (great, right) > pivot
 *
 * Pointer k is the first index of ?-part.
 */

上述情况都是在被排序的数组长度小于286的情况下的选择,当长度大于286之后,将会根据数组按照顺序大小升降的改变次数选择具体的算法。

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

在大于286的数组中,如果升降情况改变次数大于67时,那么可以推定该数组整体是无序的,那么此时更适合之前两种快速排序算法,反之,由于快速排序在有序的数组中表现不好,对于大于286总体有序的数组更合适归并排序,也是这里出现的第五种算法。

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

精妙之处在于,相比普通归并排序2分分组不同,由于先前已经记录了各个小型有序分区的情况,只需要在这基础上将各个小的有序分区作为归并排序的起点即可。

发布了141 篇原创文章 · 获赞 19 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/weixin_40318210/article/details/95406230