Algorithms | Quick Sort

Algorithm implementation please go here

There have been one after another: selection sort , bubble sort , insertion sort , Hill sort , merge sort

It can be seen that it is listed according to the performance of the sorting algorithm.

Like merge sort, quick sort is also a classic application of "divide and conquer"

Quick sort works well for a variety of random inputs, and in most cases, outperforms the other algorithms above, and only requires a limited auxiliary array (merge sort requires an additional equal amount of memory space), Its memory swap is also quite simple.

As mentioned above, like merge sort, it also uses "divide and conquer idea" for algorithm design. Therefore, quick sort is also a recursive sorting algorithm.

Algorithm Description:

Quicksort recursively splits an array into two halves and then sorts them separately.


" shuffle  " is to randomly sort the input before sorting.
The first element K(a[0]) is selected as the splitting element (used to split the array into two halves), and the array is split into three parts
  1. left (a[low]...a[j-1]),
  2. Split element, a[j]
  3. Right ( a[j+1]....a[height] )
The relationship between these three groups of subarrays is that the elements on the left are not greater than a[j], and the elements on the right are not less than a[j]

Then perform the same segmentation on the left side, and then perform the same segmentation on the right side, and the whole process is recursive.


Result Guarantees for Slicing Algorithms

a[j] must be in the correct position j

a[low]...a[j-1] is not greater than a[j]

a[j+1]....a[height] is not less than a[j]

The process of the segmentation algorithm is as follows:

In order to obtain the correct a[j], we select a[low] (the first element of the array to be split) as the split element at position j each time, and then traverse from both ends of the array: from left to Traverse the array elements in sequence from the right until a[i] >=a[low] and stop, traverse from right to left, stop when a[j]<=a[low], and then swap a[i] and a[ j] element, and then continue the previous traversal until the end of the array. The final j at the end is the correct position of the split element a[low]. The traversal process is:


The segmentation algorithm is implemented as:

private static int partition(int[] array, int low, int height) {
        //partition the array to array[low]...a[j-1], a[j], a[j+1]...a[height]
        int i = low;
        int j = height + 1;

        int value = array[low];// the partition value

        //exchange the left and right along with scan
        int temp;
        while (true) {
            while (array[++i] <= value) if (i >= height) break;
            while (array[--j] >= value) if (j <= low) break;
            if (i >= j) break;

            //exchange the array[i] and array[j]
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }

        //exchange the array[low] and array[j]
        temp = array[low];
        array[low] = array[j];
        array[j] = temp;

        return j;
    }

The specific segmentation and sorting process is as follows:


When we split the array once, we can use recursion to perform the same split sorting on the remaining arrays, and finally until the entire array is sorted

In fact, after each split, it can ensure that the elements on the left of a[j] are not greater than a[j], and the elements on the right of a[j] are not less than a[j], then you only need to continue to use the split for the remaining arrays The division algorithm is fine, but the two halves of the divided array have no internal order. In the end, the array will eventually become an ordered array under the recursive division. How to do it? The essence is similar to merge sort, In our recursive sorting, we always recursively halve the array until the length of the sub-array becomes 1, and then use the merge method to compare the elements, and this comparison is guaranteed based on the order of the sub-array itself , only when the length of the sub-array is 1, the ordered characteristics of the sub-array itself can be guaranteed. At this time, the merge (merge) can be started. Only based on this foundation can the correctness of the merge sort be guaranteed, so whether the merge sort is top-to-bottom Bottom, or bottom-up merging, before calling merge, the sub-arrays must be ordered ! Similarly, after each division, the sub-arrays that are divided are not in order, they are only not less than or not greater than a[ j], but when the length of the divided array is also 1, only two elements are left for comparison, and then it becomes ordered. From this point backtracking, all ordered arrays are combined into a larger Sorted array!

The whole sorting process is:


The red element is the segmentation element!

The implementation of the whole algorithm is:

public class QuickSort {

    public static void main(String[] args) {

        int[] nums = RandomSequence.retrieveSequence();
        long previousTime = System.currentTimeMillis();
        sort(nums, 0, nums.length - 1);
        System.out.println("Quick sort cost " + (System.currentTimeMillis() - previousTime));

    }

    public static void sort(int[] array, int low, int height) {
        if (low >= height) return;

        int j = partition(array, low, height);

        sort(array, low, j - 1);

        sort(array, j + 1, height);
    }


    private static int partition(int[] array, int low, int height) {
        //partition the array to array[low]...a[j-1], a[j], a[j+1]...a[height]
        int i = low;
        int j = height + 1;

        int value = array[low];// the partition value

        //exchange the left and right along with scan
        int temp;
        while (true) {
            while (array[++i] <= value) if (i >= height) break;
            while (array[--j] >= value) if (j <= low) break;
            if (i >= j) break;

            //exchange the array[i] and array[j]
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }

        //exchange the array[low] and array[j]
        temp = array[low];
        array[low] = array[j];
        array[j] = temp;

        return j;
    }

}

As can be seen, the recursive call to sort ( int [] array , int low , int height ) is just to arrange sensible

partition ( int [] array , int low , int height ) method call

Recursive calls are very disadvantageous on small-scale arrays, and there will be too many recursive calls on small-scale arrays, so we no longer use recursion for small-scale arrays like the optimization in merge sort, but use insertion sort, for example:

  public static void sort(int[] array, int low, int height) {
//        if (low >= height) return;
        if (low + 10 >= height) {// fast then original quick sort about 200ms at size of 1<<24
            InsertSort.sort(array, low, height);
            return;
        }

        int j = partition(array, low, height);

        sort(array, low, j - 1);

        sort(array, j + 1, height);
    }

Under the scale of tens of millions of arrays, the time can be improved by 200-300ms

There is time to introduce the three-way segmentation method. When dealing with a large number of repeated elements, it can greatly improve the sorting performance. The sorting performance will be tested later.


Algorithm Complexity Analysis


Completion time-2018-04-28 (Because the project is busy, it may be later than this time, but it will be completed as much as possible!)

Next: Heap Sort

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325811763&siteId=291194637