Interpretation of DualPivotQuicksort source code

Interpretation of DualPivotQuicksort source code Threshold constant Click to view the code /** * The maximum number of r...
General merge sort
Optimized merge sort (TimSort)
Insert sort
Pairwise insertion sort
Single axis quick sort
Two axis quick sort
Count sort
Interpretation of DualPivotQuicksort source code Threshold constant Click to view the code
/** * The maximum number of runs in merge sort. */ private static final int MAX_RUN_COUNT = 67; /** * The maximum length of run in merge sort. */ private static final int MAX_RUN_LENGTH = 33; /** * If the length of an array to be sorted is less than this * constant, Quicksort is used in preference to merge sort. */ private static final int QUICKSORT_THRESHOLD = 286; /** * If the length of an array to be sorted is less than this * constant, insertion sort is used in preference to Quicksort. */ private static final int INSERTION_SORT_THRESHOLD = 47; /** * If the length of a byte array to be sorted is greater than this * constant, counting sort is used in preference to insertion sort. */ private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29; /** * If the length of a short or char array to be sorted is greater * than this constant, counting sort is used in preference to Quicksort. */ private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200;
sort

General merge sort

It is the idea of "divide and conquer". First disassemble the sequence elements, and then merge them, that is, merge the adjacent ordered subsequences.

Optimized merge sort (TimSort)

Timport's idea is that when "dividing", it is directly divided into various ordered subsequences with different lengths from left to right, and then these subsequences are merged. In this way, the complexity is greatly reduced.

Insert sort

The sequence is divided into two parts, one is orderly and the other is disordered. Elements are continuously selected from the disordered part and inserted into the ordered part. (at first, it was thought that the first element was an ordered part and the other elements were disordered parts)

Pairwise insertion sort

Step 1: take two elements a1 and a2 in the disordered part and adjust them so that a1 > a2;
Step 2: a1 compare to the left, find the appropriate position and insert;
Step 3: a2 just compare on the left side of a1 (a1 > a2) and find the appropriate position to insert.

Single axis quick sort

Step 1: select one of the elements as the axis.
Step 2: both sides start traversing at the same time. The elements larger than the axis are placed on the left and the elements smaller than the axis are placed on the right. (see the following figure for details)
Step 3: recursively process the two sequences separated by the axis, and repeat step 1 and 2. Finally, an ordered sequence is obtained

Two axis quick sort

Step 1: k traverse to the right, great traverse to the left, and put the traversed elements into the appropriate interval.
Step 2: recursively process the three intervals to obtain the ordered sequence.

Count sort

① : first create an array count whose length is the number of elements, and all the elements in it are 0.
② : traverse the sequence to be sorted, find the position of the array count according to the sequence element size a, and count[a]+=1;
(for example, if the element just traversed is 55, count [55] + = 1 is found)
③ : traverse count [] from left to right, and take out all elements that are not 0, according to the number of count[a].
④ : finally get the valid sequence.

Source code Click to view the code
/* * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package java.utill; /** * 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. * Here are some advantages of double axis fast row. For many data arrangement modes that cause low efficiency after two cycles, * Both maintain O(nlogn) complexity * * All exposed methods are package-private, designed to be invoked * from public methods (in class Arrays) after performing any * necessary array bounds checks and expanding parameters into the * required forms. * An introduction to reusing this private class * * @author Vladimir Yaroslavskiy * @author Jon Bentley * @author Josh Bloch * * @version 2011.02.11 m765.827.12i:5\7pm * @since 1.7 */ final class DualPivotQuicksort { /** * Prevents instantiation.Prevent this class from being instantiated */ private DualPivotQuicksort() { } /* * Tuning parameters.Some set threshold data, which are proved to be optimal by experiments */ /** * The maximum number of runs in merge sort. * Maximum number of sequences to be merged */ private static final int MAX_RUN_COUNT = 67; /** * If the length of an array to be sorted is less than this * constant, Quicksort is used in preference to merge sort. * If the length of the array participating in sorting is less than this value, quick sorting is preferred instead of merge sorting */ private static final int QUICKSORT_THRESHOLD = 286; /** * If the length of an array to be sorted is less than this * constant, insertion sort is used in preference to Quicksort. * If the length of the array participating in sorting is less than this value, consider inserting sorting instead of quick sorting */ private static final int INSERTION_SORT_THRESHOLD = 47; /** * If the length of a byte array to be sorted is greater than this * constant, counting sort is used in preference to insertion sort. * This is a byte array */ private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29; /** * If the length of a short or char array to be sorted is greater * than this constant, counting sort is used in preference to Quicksort. * This is the threshold for enabling count sorting for short or char arrays */ private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200; /* * Sorting methods for seven primitive types. * For the seven basic types of sorting methods, this blog only discusses int types and short types */ /** * Sorts the specified range of the array using the given * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted * @param work a workspace array (slice) * @param workBase origin of usable space in work array * @param workLen usable size of work array */ static void sort(int[] a, int left, int right, int[] work, int workBase, int workLen) { // Use Quicksort on small arrays //Small sequences directly use fast sorting. 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). * run[i] It means the starting position of the ith ordered sequence (ascending or descending) */ int[] run = new int[MAX_RUN_COUNT + 1]; int count = 0; run[0] = left; // Check if the array is nearly sorted // Check whether the array is close to the ordered state. This loop is used to split an ordered sequence for (int k = left; k < right; run[count] = k) { // Equal items in the beginning of the sequence //Skip some equal elements at the beginning of the sequence while (k < right && a[k] == a[k + 1]) k++; if (k == right) break; // Sequence finishes with equal items, run[count]~k all elements are equal if (a[k] < a[k + 1]) { // ascending while (++k <= right && a[k - 1] <= a[k]) ;//run[account]~k is in ascending order } 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; } //After processing, run[account]~k is in ascending order (including equality) } // Merge a transformed descending sequence followed by an // ascending sequence //Merge two ascending sequences, the first of which is transformed from descending sequence. See Figure 1 for details 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 the sequence to be merged exceeds the threshold, indicating that the sequence is not highly close to order, use quick sorting instead */ if (++count == MAX_RUN_COUNT) { sort(a, left, right, true); return; } } // These invariants should hold true: // run[0] = 0 // run[<last>] = right + 1; (terminator) //These three judgments are used to deal with the termination of the last subsequence if (count == 0) { // A single equal run //If all elements of this sequence are equal, it is returned directly. It is obviously caused by the above break breaking cycle. 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. //This indicates that the sequence is already a ascending sequence and is returned directly return; } /* * There are two triggers in this place: * 1,run[count]==right(Before adding 1) * 2,Triggered by the above break, that is, run[count] is followed by a series of numbers with equal elements * In this way, the start must be set for the next subsequence that is not in the (left~right) range, * This terminates the last subsequence in the range * See Figure 2 for details * At the same time, compared with the above else if, it is also a behavior related to termination conditions */ 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; } // Determine alternation base for merge //Determine the cardinality group that produces the change, that is, whether it changes on work [] or a [] (see the first if else below) //I really don't understand this optimization byte odd = 0; //The relationship between n and odd is known from the following statement: //odd 0 1 0 1 ...... //n 1 2 4 8 ...... for (int n = 1; (n <<= 1) < count; odd ^= 1) ; // Use or create temporary array b for merging //Create a temporary array b for the merge process 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; } //Decide whether a and b point to the original a [] or work [] 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 // Merge // The outermost loop indicates that the merging is successful until the count is 1, that is, there is only one sequence to be merged in the stack // a is the original array and b is the temporary array for (int last; count > 1; count = last) { // Traverse the array and merge two adjacent ascending sequences for (int k = (last = 0) + 2; k <= count; k += 2) { // Merge run[k-2] and run[k-1] sequences 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]; } } // Here, move the merged sequence forward run[++last] = hi; } // If the length of the stack is odd, copy the last single ordered sequence across the opposite side if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; b[i + bo] = a[i + ao] ) ; run[++last] = right; } //Why exchange a and b here? See Figure III for details int[] t = a; a = b; b = t; int o = ao; ao = bo; bo = o; } } /** * Sorts the specified range of the array by Dual-Pivot Quicksort. * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted * @param leftmost indicates if this part is the leftmost in the range */ private static void sort(int[] a, int left, int right, boolean leftmost) { int length = right - left + 1; // Use insertion sort on tiny arrays //Small arrays are sorted using inserts if (length < INSERTION_SORT_THRESHOLD) { if (leftmost) {//Represents that the sequence to be compared is at the far left of the array /* * Traditional (without sentinel) insertion sort, * optimized for server VM, is used in case of * the leftmost part. * Classic insertion sorting algorithm, without sentry. Optimized and used in the leftmost case */ 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. * First, cross the ascending part of the beginning */ 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. * Insert and sort in pairs, as shown in Figure 4 * Specific execution process: the top data that has been arranged in the above do while loop ​ ​ ​ * (1)For the data to be inserted, the first value is assigned a1 and the second value is assigned a2, ​ ​ ​ * (2)Then judge the size of a1 and a2 to make a1 > a2 (key point) ​ ​ ​ * (3)Next, first insert the large value a1 and compare a1 with the numbers before k one by one, * Insert a1 into the appropriate position until the value is less than a1, * Note: the distance here is 2. The purpose is to leave an insertion gap for a2 ​ * (4)Next, insert a small value a2 and compare a2 with the number before k at this time one by one, * (This k has changed to the left of a1, that is, at this time, just insert a2 to the left of a1, * Therefore, the comparison times of a2 are reduced, and the optimization occurs here) * Insert a2 into the appropriate position until the value is less than a2, * Note: the distance here is 1 * (5)Finally, insert the last data not traversed into the appropriate location * * Another question: why can't it be leftmost? * In the case of leftmost, left boundary crossing may occur * */ 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; } //Start the axis selection of double axis fast platoon // Inexpensive approximation of length / 7 // A low complexity implementation 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. * Take five elements close to the middle, and the interval between these five positions is length/7, * These five elements are sorted, and these elements will eventually be used as axes (see Figure 5) */ 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 //Use insert sort to sort the elements in these five positions 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 position of the first element of the middle region int great = right; // The position of the first element in the right area //If the following conditions are met, e2 and e4 shall be used for double axis fast platoon, otherwise e3 shall be used for single axis fast platoon 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. * Using the elements at e2 and e4 of the five elements as the axis, they are roughly in the quartile position. * 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. * Here is to cover the elements on e2 and e4 with the first and last elements, * In this way, the elements on e2 and e4 are excluded from the following sort because they are already pivots */ a[e2] = a[left]; a[e4] = a[right]; /* * Skip elements, which are less or greater than pivot values. * Skip some values less than pivot 1 at the head of the queue and values greater than pivot 2 at the end of the queue */ 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. * The idea here is: k keep moving to the right and put the elements into the corresponding area until they hit great. */ outer: for (int k = less - 1; ++k <= great; ) { int ak = a[k]; if (ak < pivot1) { // Move a[k] to left part //Move to the 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[i] = b; i--;"The efficiency of is higher than that of "a[i--] = b;" */ a[less] = ak; ++less; } else if (ak > pivot2) { // Move a[k] to right part //Move to the right part while (a[great] > pivot2) {//Here, first put a wave larger than privot2 in the right part if (great-- == k) { // k encounters great, and this segmentation is terminated 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 /* * Because the sorted part is a[left+1...right-1], while the original range is a[left...right], * The two deducted elements are pivot 1 and pivot 2, * Therefore, after exchange, pivot 1 and pivot 2 are inserted into a[less-1] and a[great+1], * As the real pivot, it maintains three parts: left part, center part and right part */ 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 //Recursive fast sorting of left and right sequences 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 the center area is too large, it exceeds 4 / 7 of the length of the array. First preprocess and then participate in recursive sorting. * The preprocessing method is to put the elements equal to pivot 1 on the left, * The elements equal to pivot 2 are uniformly placed on the right, and finally a sequence without pivot 1 and pivot 2 is generated, * Then take it to participate in the recursion in the fast row. */ 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 //Move to the left part a[k] = a[less]; a[less] = ak; ++less; } else if (ak == pivot2) { // Move a[k] to right part //Move to the left 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); } else { // Partitioning with one pivot //Partition with uniaxial 3-way, because e1-e5 has at least one pair of equal elements, // Therefore, it is determined that there are most duplicate elements in this array /* * Use the third of the five sorted elements as pivot. * This value is inexpensive approximation of the median. */ int pivot = a[e3]; /* * 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. * The single axle quick row is similar to the double axles above. A dynamic diagram introduces Figure 5 */ 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; } } /* * Sort left and right parts recursively. * All elements from center part are equal * and, therefore, already sorted. * Recursive fast scheduling */ sort(a, left, less - 1, leftmost); sort(a, great + 1, right, false); } } /** * Sorts the specified range of the array using the given * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted * @param work a workspace array (slice) * @param workBase origin of usable space in work array * @param workLen usable size of work array * Statistical sorting is applicable when the number of elements is much larger than the number of elements, * It is applicable to types with a small number of elements such as Short, Byte and Char. */ static void sort(short[] a, int left, int right, short[] work, int workBase, int workLen) { // Use counting sort on large arrays if (right - left > COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR) { int[] count = new int[NUM_SHORT_VALUES]; //From left to right, add 1 to the count of the existing number for (int i = left - 1; ++i <= right; count[a[i] - Short.MIN_VALUE]++ ); //From right to left, extract the number whose count is not 0, so that the sequence becomes ordered 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); } } else { // Use Dual-Pivot Quicksort on small arrays //doSort(a, left, right, work, workBase, workLen); //The doSort above is similar to the previous fast sort, so I won't repeat it here. } } /** The number of distinct short values. */ private static final int NUM_SHORT_VALUES = 1 << 16; }

23 November 2021, 12:14 | Views: 7313

Add new comment

For adding a comment, please log in
or create account

0 comments