The realization java.util.DualPivotQuickSort

DualPivotQuickSort collection of various sorting algorithm, called DualPivotQuickSort not appropriate. Different sorting algorithms have different usage scenarios. This file is read, sorting algorithm even if they get to know thoroughly.
This article describes only useful code fragments, DualPivotQuickSort.java code snippets can be pieced together.
Sorting herein to array a [left, right] closed interval sorted.

constant

  • QUICKSORT_THRESHOLD = 286
    smaller than this value using fast discharge, this value merge sort.
  • INSERTION_SORT_THRESHOLD = 47
    less than this value using an insertion sort, quicksort using this value.
  • COUNTING_SORT_THRESHOLD_FOR_BYTE = 29, COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200
    when the byte array is sorted, if the number of elements more, then use counting sort, that is, use a bucket containing 256 elements to sort.
    When sorting an array of short, if the number of elements more, then use counting sort, that the use of 65,536 barrels of sorts.
  • NUM_SHORT_VALUES, NUM_BYTE_VALUES, NUM_CHAR_VALUES
    represent several short, byte, char data type, for sorting counting.
  • MAX_RUN_COUNT = 67
    the number of run merge sort.

In addition QUICKSORT_THRESHOLD constants are used to select the rest of the sorting algorithm, algorithm selection sort main consideration the number of elements.

  • When the number of elements is much greater than the number of elements species, counting sorting using
  • When the number of elements and substantially more ordered (less incremented fragment), and merge sort
  • When the number of elements more and more disorderly, using quick sort
  • When the small number of elements, using an insertion sort

Ordinary insertion sort

for (int i = left, j = i; i < right; j = ++i) {
    long ai = a[i + 1];
    while (ai < a[j]) {
        a[j + 1] = a[j];
        if (j-- == left) {
            break;
        }
    }
    a[j + 1] = ai;
}

Improved insertion sort: insert sequencing pairs

The pair of insertion sort is to improve insertion sort, every two elements will move forward together.
It requires about Pretreatment: skip the first order segment, the length of this fragment must be 1 or greater.
The advantage of this is that the pre-treatment: avoid edge detection on the left. In the code "normal insertion sort" section, the need for boundary detection.

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

The pair of insertion sort process, left represents the second element, k denotes the first element.

for (int k = left; ++left <= right; k = ++left) {
    long 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;
}
long last = a[right];//因为是成对排序,最后一个元素有可能落单

while (last < a[--right]) {
    a[right + 1] = a[right];
}
a[right + 1] = last;
}

Ordinary fast row: Single row fast

Quick drain is uniaxially conventional Quicksort, select only one pivot about the array into two portions. Quick drain of the most important is to select the pivot, which directly determines the performance of sorting algorithms.
Most people fast write discharge, a first value taken Pivot element, or to a random index, this index is assigned to the first element and a switching element as a pivot.
The quick drain DualPivotQuickSort uniaxial pivot selection method is this: First section 5 spots found from [left, right], this value of five points of insertion sort; e3 select pivot performed as fast row.

int seventh = (length >> 3) + (length >> 6) + 1;//length的近似七分之一,这种写法太炫酷
int e3 = (left + right) >>> 1; // The midpoint
int e2 = e3 - seventh;
int e1 = e2 - seventh;
int e4 = e3 + seventh;
int e5 = e4 + seventh;

When insertion sort these five elements, the direct use of if-else achieve insertion sort.

// Sort these elements using insertion sort
if (a[e2] < a[e1]) { long t = a[e2]; a[e2] = a[e1]; a[e1] = t; }

if (a[e3] < a[e2]) { long 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]) { long 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]) { long 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; }
        }
    }
}

Quick drain uniaxial Code: left, right section represents be sorted, less, great represents about two pointers, equal left and right at the beginning. The quick drain code is the traditional two-pointer fast row, it has many kinds of writing.

for (int k = less; k <= great; ++k) {
    if (a[k] == pivot) {
        continue;
    }
    long 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;
    }
}
sort(a, left, less - 1, leftmost);
sort(a, great + 1, right, false);

Quick sort, the float equal attention is not exactly equal, when writing this is a quick-drain easily overlooked point. If you overwrite a digital direct use pivot values ​​may cause the value of the sorted array of changes.

Improved fast row: biaxial fast row

The biaxially quick drain is to use two arrays pivot division, into the array (negative infinity, pivot1), [pivot1, pivot2], (pivot2, infinity) of three parts. pivot1 pivot2 and take a [e2] and a [e4].

//选择两个pivot
int pivot1 = a[e2];
int pivot2 = a[e4];
//把left和right放在e2、e4处,让它们参与排序过程,因为只有[left+1,right-1]区间上的数字才参与排序
a[e2] = a[left];
a[e4] = a[right];
//先贪心地快速移动一波
while (a[++less] < pivot1);
while (a[--great] > pivot2); 
//利用双轴把数组分成三部分,和快排相似
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];
            a[less] = a[great];
            ++less;
        } else { // pivot1 <= a[great] <= pivot2
            a[k] = a[great];
        } 
        a[great] = ak;
        --great;
    }
}

// 让开头和结尾的pivot1和pivot2回归到中间来
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);
sort(a, less, great, false);

In the above code, the array is divided into three regions, three regions are recursive invocation sequencing.
Wherein, in the middle sorting section sort(a, less, great, false)when there is a technique of: [less, great] interval is divided into (strictly equal to the interval pivot1), (a value between pivot1 and pivot2), (pivot2 exactly equal the interval).

//老规矩,快速走一波
while (a[less] == pivot1) ++less;
while (a[great] == pivot2) --great;
//又是一个双轴划分过程
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]; 
            a[less] = pivot1;
            ++less;
        } else { // pivot1 < a[great] < pivot2
            a[k] = a[great];
        }
        a[great] = ak;
        --great;
    }
}

After this processing, it is possible that the [less, great] interval as small as possible, and then between the digital (pivot1, pivot2) sort.

Why fast-axis parallelism ordinary fast row fast?
Performance theory, analysis of sorting algorithms mainly depends on the number of comparisons elements. Biaxial quick drain-less ordinary fast row number of comparisons.
However, compare the number of elements actually not a true reflection of the performance of sorting algorithms. When the theory with the actual situation of non-compliance, if the actual situation is not wrong, then the theory is wrong.
Statistics from the inside over the past 25 years, CPU average annual growth rate of 46%, while the memory bandwidth grew by only 37 percent per year, then after 25 years of this uneven development, the gap between them has been quite big. If this is not equitable, sustainable and sustained development, CPU speed and then one day does not make a program grow faster, because the CPU always waiting for memory transmission of data, this is the legendary Memory Wall (Memory Wall). Sorting process is the bottleneck that memory without CPU, it's like bucket theory: bucket capacity is determined by the shortest piece of board. 25 years ago, Dual-Pivot faster than the classic row could really fast row slowly, but after 25 years, although the algorithm or algorithms before that, but the computer has not before a computer. In today's computer inside Dual-Pivot algorithm faster!

Then the method is relatively light since the number of elements in this calculation comparison sorting algorithm complexity algorithm has been unable to objectively reflect the pros and cons, then how should an algorithm to evaluate it? The authors propose an algorithm called the number of scanning elements.
Inside this new algorithm, we have access to an element inside the array: array [i] is called a scan. But for the same index, and the corresponding value does not become, even if many times we visit only counted once. And we do not care in the end this is a read or write access.

In fact, this so-called scanning element number of the reaction is the size of data traffic between the CPU and memory.

Because the memory is relatively slow, the statistical size of the data flow between the CPU and memory also put the relatively slow memory factors into consideration and, therefore, the number of elements than the relatively better reflect the performance of the algorithm in the moment inside the computer.

Improved merge sort: TimSort

The array is divided into a plurality of incremental segments

The following code array is divided into several segments increment, decrement if you encounter an array of fragments will try to make it flip incremented.
If the increment fragment too much (more than MAX_RUN_COUNT), illustrate the array is too messy, use merge sort results are not good enough, then you should use a quick sort.

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) {
    // Equal items in the beginning of the sequence
    while (k < right && a[k] == a[k + 1])
        k++;
    if (k == right) break;  // Sequence finishes with equal items
    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]);
        // Transform into an ascending sequence
        for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
            int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
        }
    }

    // Merge a transformed descending sequence followed by an
    // ascending sequence
    if (run[count] > left && a[run[count]] >= a[run[count] - 1]) {
        count--;
    }

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

After determining the incremental segment, if only an incremental fragment was found, then the result is already ordered, and direct return.

if (count == 0) {
    // A single equal run
    return;
} else if (count == 1 && run[count] > right) {
    // Either a single ascending or a transformed descending run.
    // Always check that a final run is a proper terminator, otherwise
    // we have an unterminated trailing run, to handle downstream.
    return;
}
right++;
if (run[count] < right) {
    // Corner case: the final run is not a terminator. This may happen
    // if a final run is an equals run, or there is a single-element run
    // at the end. Fix up by adding a proper terminator at the end.
    // Note that we terminate with (right + 1), incremented earlier.
    run[++count] = right;
}

Non-recursive manner merge sort

Merge sort space complexity is O (n), n is the number of elements. This signature is a function static void sort(int[] a, int left, int right,int[] work, int workBase, int workLen), expressed in the array a [left, right] the sorting section, the additional space available for the sorting process in the work [workBase, workLen]. If a given work space is not enough, it will open up new enough space.

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

Sorting statistics

Sorting statistics apply to the number of elements is much greater than the number of elements in the case thereof, suitable for a small number of element types Short, Byte, Char and other types. Short, for example the following code to perform statistical sorting.

int[] count = new int[NUM_SHORT_VALUES];

for (int i = left - 1; ++i <= right;
    count[a[i] - Short.MIN_VALUE]++
);
for (int i = NUM_SHORT_VALUES, k = right + 1; k > left; ) {
    while (count[--i] == 0);
    short value = (short) (i + Short.MIN_VALUE);
    int s = count[i];

    do {
        a[--k] = value;
    } while (--s > 0);
}

to sum up

Array.sort () function is difficult to say what kind of sorting algorithm used, because it took several sorting algorithms. The fundamental reason lies in the different sorting algorithms have different usage scenarios. Array.sort () function defines a set of empirically derived routing algorithm to achieve a constant, it is worth learning place.

Reference material

https://baike.baidu.com/item/TimSort/10279720?fr=aladdin
https://www.jianshu.com/p/2c6f79e8ce6e
https://www.jianshu.com/p/2c6f79e8ce6e

Guess you like

Origin www.cnblogs.com/weiyinfu/p/10963062.html