9.4. Quicksort
Quicksort
(by convention, the
Here's the plan:
The process is
Figure 9-24. Quicksort works by dividing the array into "small" and "large" numbersin this case, numbers less than or equal to 4 and numbers greater than 4. Each section is then recursively sorted.
As in merge sort, the primary
Figure 9-25. The methods quicksort() and quicksortHelper() .
All of the hard work is done in the helper method
partition()
. The partitioning algorithm begins by choosing some array element as the
pivot
. We arbitrarily choose the last number in the region being partitioned as the pivot (Figure 9-26). Numbers less than or equal to the pivot are considered small, while numbers greater than the pivot are
Figure 9-26. The partitioning algorithm chooses one number as the pivot. Four regions are
|
1 /** 2 * Choose one element of data in the region between bottom and top, 3 * inclusive, as the pivot. Arrange the numbers so that those less 4 * than or equal to the pivot are to the left of it and those 5 * greater than the pivot are to the right of it. Return the final 6 * position of the pivot. 7 */ 8 protected static int partition (int[] data, int bottom, int top) { 9 int pivot = data[top]; 10 int firstAfterSmall = bottom; 11 for (int i = bottom; i < top; i++) { 12 if (data[i] <= pivot) { 13 swap(data, firstAfterSmall, i); 14 firstAfterSmall++; 15 } 16 } 17 swap(data, firstAfterSmall, top); 18 return firstAfterSmall; 19 } 20 21 /** Swap the elements of data at indices i and j. */ 22 protected static void swap (int[] data, int i, int j) { 23 int temp = data[i]; 24 data[i] = data[j]; 25 data[j] = temp; 26 } |
The index firstAfterSmall is the index of the first element that is not known to be small. When we swap element firstAfterSmall with element i on line 13, we are usually swapping element i with the first known large number. If there are no known large numbers yet, firstAfterSmall equals i , so we simply swap the element with itself; as desired, this has no effect.
Similarly, on line 17, firstAfterSmall is the index of either the first large number or (if there are no large numbers) the pivot. In either case, swapping the pivot into this position is correct.
An invocation of
partition()
takes time linear in the length of the array being sorted, because there are
n
n
/2
large numbers. We ignore the floor and ceiling by
Since this is
The solution to this recurrence is:
Quicksort takes quadratic time in the worst case.
While the analysis is beyond the scope of this book, it turns out that Quicksort takes time in Q ( n log n ) in the average case. Quicksort is therefore better than insertion sort, but not as good as merge sort.
Since it has a low constant factor associated with its running time, and operates in place, Quicksort is sometimes used instead of merge sort when
n
is not expected to be very large. There are also a number of minor changes (see the Exercises) which can be made to the algorithm to greatly decrease the
The class java.util.Arrays has several overloaded versions of the static method
sort()
. The ones for arrays of primitive types use an optimized version of Quicksort that makes the worst-case behavior
| 9.13 |
What is the order of the running time of Quicksort if data is originally in reverse sorted order? What if all the elements are identical? |
| 9.14 |
Add a quicksort() method to the SortableArrayList class from Section 8.4. |
|
|
|
| 9.15 |
Modify quicksortHelper() so that, before invoking partition() , it swaps a random element in the region being sorted with the last element. With this change, no particular input can consistently produce the worst-case behavior. |
| 9.16 |
Suppose the variable numbers , of type List<Integer>, contains many numbers in no particular order. How can it be sorted in one line of code? (Hint: See the java.util.Collections class in the API.) |