Quick sorting is an in-situ sorting algorithm. Its Worst run time is N2, the expected run time is nlgn, and the hidden constant factor is small. Therefore, quick sorting is usually the best practical choice for sorting. Section 7.3 describes a randomization deformation of quick sorting. The average running time of this version is good, and there is no special input that will lead to the worst running status.
7.1 description of fast sorting
Like merging and sorting, quick sorting is also based on the grouping mode. The following are three steps to sort the sub-array a [p... R.
Decomposition: array a [p... r] is divided into two sub-arrays A [p... q-1] And a [q + 1, R], making a [p... each element in the Q-1] is less than or equal to a [Q], and a [Q] is less than or equal to a [q + 1... in R], the subscript Q is also calculated in this division process.
Solution: By recursively calling the quick sort, the sub-array a [p... q-1] And a [q + 1... R] are sorted.
Merge: No need to merge
The following process enables quick sorting:
Quicksort (A, P, R)
1 If P <r
2 then q <-- partition (A, P, R)
3 quicksort (A, P, q-1)
4 quicksort (A, q + 1, R)
The key partition process of the Quick Sort Algorithm, which is used to rearrange the sub-array a [p... R] in place:
Partition (A, P, R)
1 x <-- A [R]
2 I <P-1
3 For j <-- p to R-1
4 do if a [J] <= x
5 then I <-- I + 1
6 exchange a [J] <--> A [I]
7 exchange a [I + 1] <--> A [R]
8 return I + 1
In the above process, variable I, j divides a [p... r-1] into three sections:
(1) P <= k <= I has a [k] <= X;
(2) For I <k <J has a [k]> = X;
(3) j <= k <= R-1 is an unknown segment that has not been compared.
The nature of these three segments is exactly the cycle-changing pattern in the above process.
7.2 performance of fast sorting
The running time of quick sorting is related to the symmetry of the Division, and the two are related to the selected element. If the division is symmetric, the algorithm is as fast as the merge algorithm in a progressive sense. If the division is asymmetric, it is as slow as the insert algorithm.
Worst Case Division
The worst case is that the two regions produced by the Division Process contain 0 and n-1 elements, respectively. Assuming that each recursive call of the algorithm produces this asymmetric division, the running time can be recursively expressed:
T (n) = T (n-1) + T (0) + random (n) = T (n-1) + random (n) = random (N2)
Best case Division
When the two regions produced by the Division contain n/2 elements, the optimal division is generated. In this case
T (n) = T (n/2) + hour (n) = hour (nlgn)
Balanced Division
The average performance of quick sorting is very similar to the best case, rather than the worst case.
Assume that the division process always produces a Division. At first glance, this division is very unbalanced, and the Quick Sort run time can be recursively expressed:
T (n) = T (9n/10) + T (N/10) + random (N)
Use the recursive tree to obtain T (n) = variance (nlgn ).
In fact, any division by constant ratio will produce a recursive tree with a depth of least (lgn). The cost of each layer is O (n ), the total running time is O (nlgn ).
7.3 randomized version of quick sorting
To avoid the worst division caused by specific input, you can add randomization components to the algorithm to achieve better average performance. Many people think that the Randomization version of quick sorting is an idealized choice for large enough input. For quick sorting, there is no need to randomize the input as described in section 5.3. Here we use a different randomization technique called random sampling. In this method, a [R] is not always used as the principal component, but an element is randomly selected from the subarray A [p... R.
Compared with the original algorithm, the new division process is as follows:
Randomized-partition (A, P, R)
1 I <-- random (P, R)
2 exchange a [R] <--> A [I]
3 return partition (A, P, R)
7.4 Quick Sort Analysis
7.2 intuitively discuss the worst case of fast sorting and why it runs fast. This section will strictly analyze the fast sorting performance. Perform the worst case analysis first, which is the same for both the Randomization version and the non-Randomization version. The average performance of the Randomization version is analyzed.
Worst case
The replacement method (section 4.1) can prove that the worst running time of fast sorting is O (n2 ):
T (n) = max0 <= q <= n-1 {T (q) + T (n-q-1)} + round (N)
Guess T (n) <= cn2, C is a constant.
T (n) <= max0 <= q <= n-1 {cq2 + C (n-q-1) 2} + round (n) = C * (Q2 + (n-q-1) 2) + round (n) <= cn2-c (2n-1) + round (N)
C (2n-1) can be used to control round (N) and T (n) <= cn2.
It can also prove t (n) = Ω (N2 );
Expected randomization Performance
The time for fast sorting is mainly determined by the time spent on partition. Whenever partiton is called, a primary element is selected, and subsequent recursion will not involve this primary element, therefore, during the entire sorting process, partiton can be called up to n times. The time for calling a partition is O (1) plus the number of times the elements in the for loop are compared, if we can know the total number of element comparisons, we can know the running time of the quick sort.
Assume that the number of times the element is compared is X, then the algorithm Runtime is O (n + x ).
In order to obtain the number of element comparisons, we need to analyze when two elements are compared and when they are not compared. To this end, rename the element Z1, Z2 ,..., zn and Zi are elements smaller than I in the array, and also define zij = {zi ,..., ZJ} is a set of elements between Zi and ZJ.
During the partition process, each element is compared with the primary element and will not be compared with the primary element. This means that the two elements can be compared at most once. This feature allows us to analyze the problem with random indicator variables:
Xij = I {comparison between Zi and ZJ}
When will the algorithm compare Zi and ZJ, and when will the two elements not be compared? To compare, one of the elements must be selected as the primary element in a recursive hierarchy, and both elements are still in this division. In a division containing zij, the element Zi <x <ZJ is selected as the primary element, so Zi and ZJ have no chance to compare. Therefore, the comparison between Zi and ZJ depends on which element in zij is first selected as the primary element (when the element outside zij is selected as the primary element does not affect ).
P {Xij = 1} = P {zi and ZJ for comparison} = P {zi or ZJ is first selected as the primary element in zij} = P {zi is first selected as the primary element in zij.} + P {ZJ is selected as the primary element first in zij} = 2/(J-I + 1 ).
E [x] = Σ I = 1 ~ N-1 Σ J = I + 1 ~ N 2/(J-I + 1)
= Sigma I = 1 ~ N-1 Σ k = 1 ~ N-I 2/(k + 1)
<= Σ I = 1 ~ N-1 Σ k = 1 ~ N 2/k = O (nlgn)
Exercise:
7 .. for insert sorting, the running time is very fast when the input is "almost" sorted. In practice, we can make full use of this feature to improve the running time of quick sorting, when a fast sort is called in a subarray with a length less than K, it is returned without any sort. When the top-level quick sort is returned, the entire array is inserted and sorted once. The expected running time of this algorithm is O (NK + NLG (N/K )). How to Select K in practice.
Analysis: (1) the running time of the algorithm consists of two parts: the first is the fast sorting time, but the insert sorting time. The only difference between the former and the standard quick sorting time is that the depth of recursion is relatively low. Refer to the recursive tree used to analyze the algorithm execution time, the height of the recursive tree of the standard algorithm is O (lgn), and the height of the recursive tree of the algorithm is O (lgn-lgk ). Therefore, the running time is O (nlgn/K ). Then let's look at the insert sort. In this case, the arrays are finally divided into A1, A2 ,..., am M blocks. The number of elements in each block is K1, K2 ,..., km, with Ki <= K, Σ ki = n. Therefore, the algorithm complexity is O (K12 + k22 +... + km2.) = O (k1k + k2k +... + KKM) = O (kN ).
(2) In practice, I think we need to find the critical point of making insertion sort faster than K.
Questions
7.1 Hoare Division
The partition algorithm provided in this chapter is not in its original version. The Hoare algorithm is provided in the West.
HPARE-PARTITION (A, P, R)
1 x <-- A [p]
2 I <P-1
3 j <-- R + 1
4 While true
5 do repeat j <-- J-1
6 until a [J] <= x
7 do repeat I <-- I + 1
8 untile A [I]> = x
9 if I <j
10 then a [I] <--> A [J]
11 else return J
Hoare division is different from partition. instead of dividing an array into two parts around the principal component, the principal component is also placed in one of the parts, make sure that all elements in the first part are smaller than or equal to all elements in the last part. If hoar division is used, the subject process of quick sorting must be modified accordingly.
Verify the correctness of the above process.
(1) verify that a [p... r], assuming P <= R: the first round of the cycle will not cross the line, because at least a [p] will stop the inner loop; for the same reason, the 8th rows will not cross the border. In the subsequent loop, due to I <J, and a [I] <= X, a [J]> = x. Rows 6th and 8th do not cross-border.
(2) then it is proved that the non-changing cycle has a [k]> = x for j <k <= R. To help prove another property: A. Before the cycle starts, j = <k <= R has a [k]> = x. Before the first cycle starts, property A is true. In the loop process, rows 5 and 6 have a [k]> = x for k> J. If this loop is not completed, a [J]> = X, then a is set up before each loop. It has been proved that the variant was established before the loop. If a is set before each cycle starts, five or six rows at the end of the cycle are enough to ensure that the cycle is not changed.
It can also prove another non-variant: P = <k <I have a [k] <= x. When the loop ends, I> = J is combined with the preceding properties. We can see that the element in a [p... J] is less than or equal to the element in a [J + 1... R.
7.4 stack depth in quick sorting: Eliminating tail recursion
The quiksort algorithm contains two recursive calls to itself. The second recursion is not necessary. You can consider replacing it with iteration. This technology is called "tail recursion ".
Quiksort '(A, P, R)
1 While (P <R)
2 do q <-- partiton (A, P, R)
3 quiksort (A, P, q-1)
4 P <-- q + 1
Stack depth analysis: the above process saves the stack depth caused by the second recursion, and has no impact on the stack depth caused by the first recursion. If the first recursion is always greater than the second stack depth, this change does not reduce the stack depth even though it replaces a recursion with an iteration. In extreme cases, if q = R is always present, the stack depth is round (n ). For improvement, we can make a judgment before the first recursion, recursively apply the segments with few elements, and eliminate large segments with "tail recursion.
7.5 division of "three numbers in the middle"
An improved division method. When selecting the primary element, select the center size among the three random elements.
7.6 fuzzy sorting of intervals
Given N closed intervals, such as [ai, BI], where AI <= Bi. The goal of the algorithm is to perform fuzzy sorting on these intervals to generate an arrangement (I1, I2 ,..., in), so that there is a CJ in [AIJ, bij], satisfying C1 <= c2 <=... <= cn. Design an algorithm for sorting. The algorithm should have a general structure of the algorithm. The algorithm can be used to quickly sort the left endpoint (that is, an AI) and fully utilize overlapping areas to improve performance. In general, the expected running time of the algorithm is merge (nlgn), but when all the intervals overlap, the running time is merge (n ).
The design algorithm is as follows: during the fast sorting partition process, it records whether all the intervals of the two Divisions have the same overlap interval. If this division exists, no recursion is required.