快速排序以及常见的优化

快速排序

思想

一趟排序将数据分割成两个部分,一部分都比pivot小,一部分都比pivot大。
确定好pivot的位置。然后分别递归这两部分。

步骤

1.选pivot
2.分割
3.递归

最基本方法

public class QuickSort {
    public void quictSort(int[] nums,int low ,int high){
        int pivot;
        if (low<high){
            pivot = partition(nums,low,high);//分成两段
            quictSort(nums,low,pivot-1);//递归排序
            quictSort(nums,pivot+1,high);
        }
    }

    private int partition(int[] nums, int low, int high) {
        int pivot;
        pivot = nums[low];//划分点,基准点
        while (low<high){
            while (low<high&&nums[high]>pivot){
                high--;
            }
            swap(nums,low,high);//比pivot小的调换过去
            while (low<high&&nums[low]<pivot){
                low++;
            }
            swap(nums,low,high);//比pivot大的调换过去
        }
        return low;//重合的地方就是pivot的位置
    }

    private void swap(int[] nums, int low, int high) {
        int temp = nums[low];
        nums[low] = nums[high];
        nums[high] = temp;
    }
}

优化

为什么可以优化?
二分法最好的就是都是截取的正好都是差不多的两个部分。当有部分是排好序的时候,快排的效率会急速下降,
所以优化的点就在于如何如何选取最合适的基准点也就是pivot。

随机取pivot算法

/*随机选择枢轴的位置,区间在low和high之间*/  
int SelectPivotRandom(int arr[],int low,int high)  
{  
    //产生枢轴的位置  
    srand((unsigned)time(NULL));  
    int pivotPos = rand()%(high - low) + low;  

    //把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数  
    swap(arr[pivotPos],arr[low]);  
    return arr[low];  
} 

三值取中的算法

待排序序列的前(第一个位置)、中(中间位置)、后(最后一个位置)三个记录中的中间值(按大小排序)
作为枢轴。
        9 1 7 5 2 8 6 3 4
        ↑       ↑       ↑
        low    mid    high
        前      中      后   9>4>2 
随机数的话是需要有随机种子的。

小数组的优化

当数组的长度够小的时候,采用插入排序,不再使用快速排序。
因为长度分割到够小后,继续分割的效率要低于直接插入排序。        

优化后的代码

public class OptimizedQuickSorter extends QuickSort {
    /**
     * 插入排序的最大数组长度
     */
    private static final int MAX_LENGTH_INSERT_SORT = 7;

    /**
     * 对数组arr[low...high]的子序列作快速排序,使之有序
     */
    @Override
    public void quickSort(int[] arr, int low, int high) {
        int pivotLoc; // 记录枢轴(pivot)所在位置
        if ((high - low + 1) > MAX_LENGTH_INSERT_SORT) {
            // 待排序数组长度大于临界值,则进行快速排序
            pivotLoc = partition(arr, low, high); // 将arr[low...high]一分为二,并返回枢轴位置

            quickSort(arr, low, pivotLoc - 1);// 递归遍历arr[low...pivotLoc-1]
            quickSort(arr, pivotLoc + 1, high); // 递归遍历arr[pivotLoc+1...high]
        } else {
            // 2. 优化小数组时的排序方案,将快速排序改为插入排序
            insertSort(arr, low, high); // 对arr[low...high]子序列进行插入排序
        }
    }

    /**
     * 在arr[low...high]中利用三值取中选取枢轴(pivot),将arr[low...high]分成两部分,
     * 前半部分的子序列的记录均小于pivot,后半部分的记录均大于pivot;最后返回pivot的位置
     */
    protected int partition(int[] arr, int low, int high) {
        int pivot;
        pivot = medianOfThree(arr, low, high); // 1. 优化排序基准,使用三值取中获取中值
        while (low < high) { // 从数组的两端向中间扫描 // A
            while (low < high && arr[high] >= pivot) { // B
                high--;
            }
            // swap(arr, low, high); // 将比枢轴pivot小的元素交换到低位
            arr[low] = arr[high]; // 3. 优化不必要的交换,使用替换而不是交换  // C
            while (low < high && arr[low] <= pivot) { // D
                low++;
            }
            // swap(arr, low, high); // 将比枢轴pivot大的元素交换到高位
            arr[high] = arr[low]; // 3. 优化不必要的交换,使用替换而不是交换 // E
        }
        arr[low] = pivot; // F
        return low; // 返回一趟下来后枢轴pivot所在的位置
    }

    /**
     * 通过三值取中(从arr[low...high]子序列中)获取枢轴pivot的值,让arr[low]变成中值;并返回计算的枢轴(pivot)
     */
    private int medianOfThree(int[] arr, int low, int high) {
        int mid = low + ((high - low) >> 1); // mid = low + (high-low)/2, 中间元素下标

        // 使用三值取中得到枢轴
        if (arr[low] > arr[high]) { // 目的:让arr[low] <= arr[high]
            swap(arr, low, high);
        }
        if (arr[mid] > arr[high]) { // 目的:让arr[mid] <= arr[high]
            swap(arr, mid, high);
        }
        if (arr[mid] > arr[low]) { // 目的: 让arr[low] >= arr[mid]
            swap(arr, low, mid);
        }
        // 经过上述变化,最终 arr[mid]<=arr[low]<=arr[high],则arr[low]为中间值
        return arr[low];
    }

    /**
     * 对子序列arr[low...high]进行插入排序
     */
    private void insertSort(int[] arr, int low, int high) {
        int i, j;
        int tmp;
        for (i = low + 1; i <= high; i++) { // 从下标low+1开始遍历,因为下标为low的已经排好序
            if (arr[i] < arr[i - 1]) {
                // 如果当前下标对应的记录小于前一位记录,则需要插入,否则不需要插入,直接将记录数增加1
                tmp = arr[i]; // 记录下标i对应的元素
                for (j = i - 1; j >= low && arr[j] > tmp; j--) {
                    arr[j + 1] = arr[j]; // 记录后移
                }
                arr[j + 1] = tmp; // 插入正确位置
            }
        }
    }
}
0%