快速排序及其优化--(理论+java实现)

是一种交换排序

简单快排

算法思想:

     快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。

     然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

详见--https://cuijiahua.com/blog/2017/12/algorithm_4.html

里面提供了详细的动态求解过程

 时间复杂度分析

平均情况

最好情况

最坏情况

稳定性

java排序可视化如何实现 java快速排序优化_java排序可视化如何实现

java排序可视化如何实现 java快速排序优化_java排序可视化如何实现

java排序可视化如何实现 java快速排序优化_快速排序_03

不稳定

 

  1. 当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。
  2. 当数据随机分布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数接近相等,此时执行效率最好。

所以,数据越随机分布时,快速排序性能越好;数据越接近有序,快速排序性能越差

稳定性分析

在快速排序中,相等元素可能会因为分区而交换顺序,所以它是不稳定的算法。

代码

public void quickSort(int[] num){
        int left = 0;
        int right = num.length - 1;
        quickSortCore(num, left, right);
    }

    private void SortCore(int[] num, int left, int right) {

        //左下标一定小于右下标 否则越界
        //递归结束条件
        if (left >= right){
            return;
        }
        //对数组进行分割,取出下次分割的基准符号
        int base = division(num, left, right);
        //对基准标号左侧的一组数据进行分割
        SortCore(num, 0, base - 1);
        //右侧
        SortCore(num, base + 1, right);
    }

    private int division(int[] num, int left, int right) {

        //以最左边的数(left)为基准
        int base = num[left];
        while (left < right){
            // 从序列右端开始,向左遍历,直到找到小于base的数
            while (left < right && num[right] >= base){
                right--;
            }
            //找到了比base小的,则将其值赋给left所指向的值
            num[left] = num[right];
            // 从序列左端开始,向右遍历,直到找到大于base的数
            while (left < right && num[left] <= base){
                left++;
            }
            //找到了比base小的,则将其值赋给right所指向的值
            num[right] = num[left];
        }
        //循环结束left和right指向位置 将该位置换位base的值
        num[left] = base;
        //此时left位置左侧的值都比base小 右侧的值都比base大
        //即分割点为当前left指向的位置
        return left;
    }

优化点一:切换到插入排序

对于小数组而言, 快速排序比插入排序要慢, 所以在排序小数组时应该切换到插入排序。

只要把SortCore函数中的

//递归结束条件
if (left >= right){
    return;
}

修改为

if(right <= left + M) {  Insertion.sort(a,low, high) return; } // Insertion表示一个插入排序类

就可以了,这样的话,这条语句就具有了两个功能:

  1. 在适当时候终止递归
  2. 当数组长度小于M的时候(high-low <= M), 不进行快排,而进行插排

优化点二:基准元素选取的随机化

    在上面所有的快速排序的例子中,我们都是固定选取基准元素,这种操作做了一个假设性的前提:数组元素的分布是随机的。而如果数组不是随机的,而是有一定顺序的,甚至在最坏的情况下:完全正序或完全逆序, 这个时候麻烦就来了: 快排所消耗的时间大大延长,完全达不到快排应有的效果。

在division函数中的添加一行

我们利用随机数从原始数组中随机找一个基准元素,然后将它与数组的头元素进行替换,这样我们选出的基准元素就是随机的

swap(num, left + (int)(Math.random() * (right - left + 1)), right);

新的division函数如下

private int division(int[] num, int left, int right) {

        //以最左边的数(left)为基准
        swap(num, left + (int)(Math.random() * (right - left + 1)), right);
        int base = num[left];
        while (left < right){
            // 从序列右端开始,向左遍历,直到找到小于base的数
            while (left < right && num[right] >= base){
                right--;
            }
            //找到了比base小的,则将其值赋给left所指向的值
            num[left] = num[right];
            // 从序列左端开始,向右遍历,直到找到大于base的数
            while (left < right && num[left] <= base){
                left++;
            }
            //找到了比base小的,则将其值赋给right所指向的值
            num[right] = num[left];
        }
        //循环结束left和right指向位置 将该位置换位base的值
        num[left] = base;
        //此时left位置左侧的值都比base小 右侧的值都比base大
        //即分割点为当前left指向的位置
        return left;
}

private void swap(int[] num, int index, int right) {
        int temp = num[index];
        num[index] = num[right];
        num[right] = temp;
}

优化点三:基准元素三数取中法

一般认为, 当取得的基准元素是数组元素的中位数的时候,排序效果是最好的,但是要筛选出待排序数组的中位数的成本太高, 所以只能从待排序数组中选取一部分元素出来再取中位数, 经大量实验显示: 当筛选数组的长度为3时候,排序效果是比较好的, 所以由此发展出了三数取中法:

三数取中法: 分别取出数组的最左端元素,最右端元素和中间元素, 在这三个数中取出中位数,作为基准元素。

在division函数中修改

int base = selectMiddleOfThree(num, left, right);
private static int selectMiddleOfThree(int[] num, int left, int right) {
        int middle = left + (right - left) / 2;
        if (num[left] > num[right]){
            swap(num, left, right);
        }
        if (num[middle] > num[right]){
            swap(num, middle, right);
        }
        if (num[middle] > num[left]){
            swap(num, middle, left);
        }
        return num[left];//此时num[left]的值是三个数的中位数将其返回
}