Sword refers to offer (Java implementation) - quick sort

1. Basic principles


1.1 The basic idea of ​​quicksort:

Step 1: Shuffle the input array to prevent dependence on the input to ensure randomness

Step 2: Divide the array, for a certain j,

  • a[j] has been scheduled;
  • All elements from a[lo] to a[j-1] are not greater than a[j];
  • All elements from a[j+1] to a[hi] are not less than a[j].

Step 3: Then recursively sort the left subarray and the right subarray respectively


1.2 Pseudo code of segmentation algorithm:

private static int partition(Comparable[] a, int lo, int hi) {
        // Divide the array into three parts: a[lo .. i-1], a[i], a[i+1 .. hi]
        int i = lo,j = hi + 1; // scan pointer left and right
        Comparable v = a[lo]; // split element
        while(true){
            // Scan left and right, check if scan is over and swap elements
            while(less(a[++i], v)) if(i == hi) break; // It is also necessary to check whether the pointer i is out of bounds, less is a[++i] < v, that is, if it encounters greater than or equal to cut Stop when the element of the element value is divided
            while(less(v, a[--j])) if(j == lo) break; // also need to check if pointer j is out of bounds
            if(i >= j) break; // loop termination condition
            exch(a, i, j); // Swap the element positions of the subscripts i and j respectively
        }
        exch(a, lo, j); // put v = a[j] in the correct position, note that it is swapped with j not i
        return j;           //a[lo .. j-1] <= a[j] <= a[j+1 .. hi] 达成
    }

1.3 Quick sort pseudo code:

    public static void sort(Comparable[] a) {
        shuffle(a);
        sort(a, 0, a.length - 1);
    }
    public static void sort(Comparable[] a, int lo, int hi){
        if(lo >= hi) return ;
        int j = partition(a, lo, hi); // partition
        sort(a, lo, j - 1); // sort the left half a[lo .. j-1]
        sort(a, j + 1, hi); //sort the right half a[j+1 .. hi]
    }

2. Algorithm analysis

Notes on the algorithm:

  1. Divide in situ . Splitting can be easily achieved if an auxiliary array is used. However, copying the split array back and the extra space overhead of the array will make us lose more than the gains, so it is a better choice to directly split the original array.
  2. Don't cross the line . If the segmented element v is the smallest or largest element, it will cross the bounds. See the pseudocode of segmentation for details.
  3. Guaranteed randomness . two strategies

          a. Shuffle the initial array;

          b. When selecting a segmented element, randomly select one from the array.

  4. Terminate the loop . The loop within the split of quicksort needs to be careful to properly check for array out-of-bounds and to take into account that there may be cases where elements in the array have the same value.
  5. Handles the case where the split element value has duplicates . The left scan is best to stop when it encounters an element greater than or equal to the value of the split element, and the right scan is best to stop when it encounters an element less than or equal to the value of the split element, although it will cause some unnecessary equivalent elements Swap, but avoids some typical cases where the runtime becomes quadratic.
    Typical case: Assuming that the scan continues instead of stopping when an element of the same value as the split element is encountered, it can be proved that the running time of processing an array with only a few element values ​​is quadratic.
  6. Terminate recursion. The recursive termination condition for quicksort .

Algorithm complexity analysis:

  • time complexity

Best, Average: O(nlogn)

Worst: O(n^2), related to partitioning algorithm

  • space complexity

The space complexity is O(logn) due to the use of recursion

3. Java implementation

                     

/**
 * is a simple implementation of quicksort for an array of integers, with no optimizations:
 * 1. The input array is not shuffled, and the first one of the array is selected as the pivot, so the input dependency is not eliminated
 * 2. The left scan stops when it encounters an element greater than or equal to the value of the divided element, so as to avoid "the running time when processing an array with only several element values ​​is square level", but it will
 * Causes unnecessary exchange of identical element values, such as: all element values ​​are 5
 */
public class QuickSort{

    /**
     * This function is used to swap the positions of any two elements of the array
     * @param arr
     * @for me
     * @param j
     */
    private static void swap(int[] arr, int i, int j) {
        // robustness judgment
        if(arr == null || arr.length <= 0) {
            System.out.println("The array is empty");
            return;
        }
        //Swap the values ​​of the elements with subscripts i and j respectively
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    /**
     * This function is the division algorithm in quicksort, which implements a quicksort, taking the first element as the main element,
     * After this function runs, the elements on the left side of the pivot element are all smaller than the pivot element, and the elements on the right side of the pivot element are larger than the pivot element.
     * @param arr the array to be sorted
     * @param start
     * @param end
     * @return returns the index of the pivot after sorting
     */
    private static int partition(int[] arr, int start, int end) {
        int i = start,j = end + 1;
        //Select the first element as the primary element
        int key = arr[start];

        while(true) {
            // Scan left and right, check if scan is over and swap elements
            while(arr[++i] < key) if(i == end) break; // also need to check whether the pointer i is out of bounds, and stop when encountering an element greater than or equal to the value of the split element
            while(key < arr[--j]) if(j == start) break; // also need to check if pointer j is out of bounds
            if(i >= j) break; // loop termination condition
            swap(arr, i, j); // Swap the element positions of the subscripts i and j respectively
        }
        swap(arr, start, j); // put v = a[j] in the correct position, note that it is swapped with j not i
        System.out.println("Sort result: "+printArray(arr));
        return j;                       // a[lo .. j-1] <= a[j] <= a[j+1 .. hi] 达成

    }

    /**
     * Quick sort recursive function
     * @param arr the array to be sorted
     * @param start array start index
     * @param end array end index
     */
    private static void QuickSort(int[] arr, int start, int end) {
        if(start >= end) return;
        int j = partition(arr, start, end);        // 切分
        QuickSort(arr, start, j - 1); // sort the left half a[start .. j-1]
        QuickSort(arr, j + 1, end); // sort the right half a[j+1 .. end]
    }

    /**
     * This function is the entry function of quicksort
     * @param arr
     */
    public static void QuickSort(int[] arr) {
        // robustness judgment
        if(arr == null || arr.length <= 0) {
            System.out.println("The array is empty");
            return;
        }
        // Quicksort by recursion
        QuickSort(arr, 0, arr.length -  1);
    }

    public static String printArray(int[] arr) {
        // robustness judgment
        if(arr == null) {
            System.out.println("The array is empty");
            return null;
        }

        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < arr.length; i++){
            sb.append(arr[i] + " ");
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        //Test Case
        int[] arr = {2,12,34,34,56,623,21};
        System.out.println("before sort:" +  printArray(arr));
        QuickSort(arr);
        System.out.println("after sort:" +  printArray(arr));

    }

}



Guess you like

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