DualPivotQuicksort解读

来源:互联网 发布:java 获取特定时间 编辑:程序博客网 时间:2024/06/01 10:16

java.util.Arrays里面使用了java.util.DualPivotQuicksort作为主要的排序实现。根据JDK注释,

/** * This class implements the Dual-Pivot Quicksort algorithm by * Vladimir Yaroslavskiy, Jon Bentley, and Josh Bloch. The algorithm * offers O(n log(n)) performance on many data sets that cause other * quicksorts to degrade to quadratic performance, and is typically * faster than traditional (one-pivot) Quicksort implementations. * * @author Vladimir Yaroslavskiy * @author Jon Bentley * @author Josh Bloch * * @version 2011.02.11 m765.827.12i:5\7pm * @since 1.7 */

那么,为什么DualPivotQuicksort的性能会高呢?


1. 插入排序的改进

当数组长度较短时,使用插入排序

      if (length < INSERTION_SORT_THRESHOLD) {            if (leftmost) {                /*                 * 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;                }            } else {                /*                 * Skip the longest ascending sequence.                 */                do {                    if (left >= right) {                        return;                    }                } while (a[++left] >= a[left - 1]);                /*                 * 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.                 */                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;            }            return;        }

leftmost是个boolean变量,表明从left到right这部分,是不是数组a的最左边的部分。

if (leftmost){...}使用的是传统的插入排序,容易理解。

为了加快快排,一次排两个数,这是else部分,是优化的方式。很显然, 每次遍历插入两个元素可以减少排序过程中遍历的元素个数 。如果leftmost==true时,一次排两个数,

可能会越界。


2. 对快排的改进

常用的快排算法是从数组的left、right以及center三个数中选择一个pivot,然后在快排。而DualPivotQuicksort使用了两个pivot加速。思想如下:

1) 选择两个点作为轴心,P1,P2
2)P1必须比P2要小,现在将整个数组分为四部分:
(1)第一部分:比P1小的元素。
(2)第二部分:比P1大但是比P2小的元素。
(3)第三部分:比P2大的元素。
(4)第四部分:待比较的部分。
在开始比较前,除了轴点,其余元素几乎都在第四部分,直到比较完之后第四部分没有元素。
3).从第四部分选出一个元素a[K],与两个轴心比较,然后放到第一二三部分中的一个。
4).移动L,K,G指向。
5).重复 3)和4) 步,直到第四部分为空。
6).将P1与第一部分的最后一个元素交换。将P2与第三部分的第一个元素交换。
7).递归的将第一二三部分排序。


源码如下:

// Inexpensive approximation of length / 7        int seventh = (length >> 3) + (length >> 6) + 1;        /*         * Sort five evenly spaced elements around (and including) the         * center element in the range. These elements will be used for         * pivot selection as described below. The choice for spacing         * these elements was empirically determined to work well on         * a wide variety of inputs.         */        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; }                }            }        }        // Pointers        int less  = left;  // The index of the first element of center part        int great = right; // The index before the first element of right part        if (a[e1] != a[e2] && a[e2] != a[e3] && a[e3] != a[e4] && a[e4] != a[e5]) {            /*             * Use the second and fourth of the five sorted elements as pivots.             * These values are inexpensive approximations of the first and             * second terciles of the array. Note that pivot1 <= pivot2.             */            int pivot1 = a[e2];            int pivot2 = a[e4];            /*             * The first and the last elements to be sorted are moved to the             * locations formerly occupied by the pivots. When partitioning             * is complete, the pivots are swapped back into their final             * positions, and excluded from subsequent sorting.             */            a[e2] = a[left];            a[e4] = a[right];            /*             * Skip elements, which are less or greater than pivot values.             */            while (a[++less] < pivot1);            while (a[--great] > pivot2);            /*             * 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.             */            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);            /*             * If center part is too large (comprises > 4/7 of the array),             * swap internal pivot values to ends.             */            if (less < e1 && e5 < great) {                /*                 * Skip elements, which are equal to pivot values.                 */                while (a[less] == pivot1) {                    ++less;                }                while (a[great] == pivot2) {                    --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.                 */                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;                    }                }            }            // Sort center part recursively            sort(a, less, great, false);



























0 0
原创粉丝点击