算法

【排序知多少】快速排序详解

2020-01-07  本文已影响0人  齐鑫

快速排序思路

1、选取基准值,并标明首、尾的标识low、high;

2、先从后往前判断,当high的值大于基准值时,high--,否则将low的值替换为high的值;

3、然后从前往后判断,当low的值小于基准值时,low++,否则将high的值替换为low的值;

4、将基准值赋值给low的值;

5、当low<high的时候,递归调用2~4步;

快速排序理解

每个人理解不同,有人说快排属于交换排序方式,有人说是归并排序方式,都有道理。我理解快排就是一边分堆,一边交换。以基准值为标届,大于基准值的放在右边,小于基准值的放在左边。每分一次堆就是一次排序,直到每个堆左右只剩下0个或1个值则排序结束。

快速排序的复杂度

快排的时间复杂度和基准值的选取有绝对的关系,当基准值正好是所有元素中间大小的那个值,他的排序就是很平均的,一分为二,然后两边再平均分,最终分的深度都一样,时间复杂度也就是O(nlogn),不然的话会导致基准值两边深度及其不平衡,极端情况下就是一颗斜数,这种情况下时间复杂度就是O(n^2)。

快速排序的优化

既然上面说到,快速排序的效率和基准值的选取有绝对的关系,那么优化点自然要从如何选好基准值入手。

方法一:以首(或尾)部的值作为基准值。这种弊端显而易见,谁也不能说清选取的基准值到底是什么。

方法二:随机值选取。就是在所有元素中随便选取一个值作为基准值。这种方法比起第一种稍微保险一些,稍稍降低了取到醉倒或者最小值的概率,但是还是一种“瞎蒙”的存在。

方法三:三数取中值,即选取所有元素中的三个值,在这三个值中进行排序,选择中间的值作为基准值。这样做的好处就是能保证选取的基准值不会是最大或者最小的值。

方法四:多(大于三)数取中。同上。

快速排序的实际应用

很多人在实际的工作中应该都会用到Arrays.sort()方法进行排序,这里面其实就有实际用到快速排序。


排序.png

如上图,当需要排序的元素个数大于QUICKSORT_THRESHOLD(286)的值时,便会使用优化后的双轴快速排序方法。感兴趣的同学可以自行研究jdk源码。

至于小于QUICKSORT_THRESHOLD(286)值的时候用什么排序方式呢?答案是直接插入排序。

这其中当然包含各种排序方法适合场景的考虑,而为神马这个边界值时286呢?参考Stack Overflow的回答,那就是前任进行了多次试验,发现这个值就是算法性能的分界点。

快速排序JAVA实现代码

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 432, 2, 31, 5, 234, 12};
        quickSort(arr, 0, arr.length - 1);
        for (int a : arr) {
            System.out.println(a);
        }
    }

    /**
     * 快速排序思路:
     * 在一组元素中,选取一个基准值,把大于基准值的元素放到右侧,小于基准值的元素放到左侧,重复操作直到完成排序。
     * <p>
     * 使用方法:
     * 1、找基准值,并设置首、尾标识(low、high)
     * 2、从后半部分开始,当扫描值大于基准值时就让high-1,如果比基准值小,则把high位置的值赋值给low
     * 3、然后从前向后臊面,如果扫描至小于low则low+1,如果大于基准值则将low位置的值付给high位置
     * 4、递归操作
     *
     * @param arr
     * @param low
     * @param high
     */
    private static void quickSort(int[] arr, int low, int high) {
        // 指定递归退出条件
        if (low < high) {
            // 获取排序后基准值的位置
            int pivot = partition(arr, low, high);
            // 对小于基准值的元素再次进行排序
            quickSort(arr, low, pivot - 1);
            // 对大于基准值的元素再次进行排序
            quickSort(arr, pivot + 1, high);
        }
    }

    /**
     * 返回排序后基准值的位置
     *
     * @param arr
     * @param low
     * @param high
     * @return
     */
    private static int partition(int[] arr, int low, int high) {
        int pivotKey = arr[low];
        while (low < high) {
            // 当尾部元素大于基准值时high-1
            while (low < high & arr[high] >= pivotKey) {
                high--;
            }
            // 否则将尾部元素赋值给首部位置
            arr[low] = arr[high];
            // 当首部元素小于基准值时low+1
            while (low < high && arr[low] <= pivotKey) {
                low++;
            }
            // 否则将首部元素赋值给尾部位置
            arr[high] = arr[low];
        }
        // 将基准值插入到首部和尾部中间位置
        arr[low] = pivotKey;
        // 返回基准值的位置
        return low;
    }
}
上一篇下一篇

猜你喜欢

热点阅读