Java数据结构与算法笔记——快速排序

本文详细介绍了如何通过选择中位数作为基准,提升快速排序算法的效率,并探讨了处理小划分问题的策略,结合冒泡、选择或插入排序。通过Java代码实例展示了改进的快速排序算法及其在实际场景的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法原理

  1. 先通过第一趟排序,将数组原地划分为两部分,其中一部分的所有数据都小于另一部分的所有数据。原数组被划分为2份。
  2. 通过递归的处理,再对原数组分割的两部分分别划分为两部分,同样是使得其中一部分的所有数据都小于另一部分的所有数据。这个时候原数组被划分为了4份。
  3. 步骤1,2被划分后的最小单元子数组来看,它们仍然是无序的,但是,它们所组成的原数组却逐渐向有序的方向前进
  4. 这样不断划分到最后,数组被划分为多个由一个元素或多个相同元素组成的单元,这样数组就有序了

在这里插入图片描述

实现分割数组的思路

首先选择基准元素,一般我们选取数组中第一个元素为基准元素(假设数组是随机分布的)。
从两端分别开始,直到左边指针找到比基准元素大的元素,右边指针找到比基准元素小的元素。然后交换两个位置元素。
在这里插入图片描述
在这里插入图片描述
然后继续移动两个指针,找到满足交换条件的元素,再次交换。
在这里插入图片描述
直到左边的指针>=右边的指针。最后交换首个元素和两个指针相遇的位置的元素。
在这里插入图片描述
这样,我们就把数组分成了两部分{3,1,2,5,4,6}和{9,7,10,8},然后再对这两个部分做分割处理…

Java代码实现

package advancedsort;

import java.util.Arrays;

public class AdvancedSortTest3 {
    public static void main(String[] args) {
        int[] data = {77,66,99,88,55,44,33,22,11,9,8,7,6,5,4,3,2,1};
        quickSort(data,0,data.length-1);
        System.out.println(Arrays.toString(data));
    }

    //宏观的快速排序算法的逻辑实现,依赖递归算法应用
    public static void quickSort(int[] data, int left, int right){
        //应用的是递归的思路,边界条件是左边的指针>=右边的指针
        if(left >= right){ //递归的边界条件
            return;//结束方法
        }else {
            //递归之前把数组data分割成两部分。
            //前半部分所有元素都应小于后半部分,前<base,后>base。
            //返回的是中间位置的指针
            int partition = partitionLR(data,left,right);
            //分割后,就可以进行递归
            //在这里,我们跳过了partition位置的元素,因为它比前面的都大,比后面的都小,它的位置是正确的
            quickSort(data,left,partition-1);
            quickSort(data,partition+1,right);
        }
    }

    //实现数组分割功能
    public static int partitionLR(int[] data, int left, int right){
        int i = left;
        int j = right +1;
        int base = data[left];//基准值,默认是被分割的数组的第一个元素
        while (true){
            while (i<right && data[++i]<base){}//基准是第0个元素,所以从第一个元素开始判断,寻找大于等于基准的元素
            while (j>left && data[--j]>base){}//从最后一个元素开始,寻找比基准小的元素
            
            if (i>=j){//如果已经遍历了整个数组,就跳出循环
                break;
            }else {//交换两个元素
                swap(data,i,j);
            }
        }

        //将base处元素和j处元素交换
        swap(data,left,j);
        return j;
    }

    //实现数组元素交换的方法
    public static void swap(int[] data, int i, int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

}

快速排序算法提升

快速排序具有最好的平均性能(average behavior),但最坏性能(worst case behavior)和插入排序相同,也是O(n^2)

比如一个序列5,4,3,2,1,要排为1,2,3,4,5。按照快速排序方法,每次只会有一个数据进入正确顺序,不能把数据分成大小相当的两份,很明显,排序的过程就成了一个歪脖子树,树的深度为n,那时间复杂度就成了O(n^2)。

尽管如此,需要排序的情况几乎都是乱序的,自然性能就保证了。资料显示,在数据量小于20的时候,插入排序具有最好的性能。当大于20时,快速排序具有最好的性能,归并(merge sort)也望尘莫及,尽管复杂度都为nlog2(n)。

针对最坏性能的时候,其实我们也是可以有一些优化的办法:理想状态下,应该选择被排序数组的中值数据作为基准,也就可以让一半的书大于基准数,一半的数小于基准数,这样会使得数组被划分为两个大小相等的子数组,对快速排序来说,拥有两个大小相等的子数组是最优的情况。

但是,判断数组的中值同样会花费时间。所以我们一般采用三项取中划分

取数组中第一个、中间的、最后一个,选择这三个数中位于中间的数

提升效率后的算法实现

package advancedsort;

import java.util.Arrays;

public class AdvancedSortTest2 {
    public static void main(String[] args) {
        int[] data = {66,55,44,33,22,99,88,77,11,9,8,7,6,5,4,3,2,1};
        quickSort(data,0,data.length-1);
        System.out.println(Arrays.toString(data));
    }

    //宏观的快速排序算法的逻辑实现,依赖递归算法应用
    public static void quickSort(int[] data, int left, int right){
        //应用的是递归的思路,边界条件是左边的指针>=右边的指针
        if(left >= right){ //递归的边界条件
            return;//结束方法
        }else {
            //递归之前把数组data分割成两部分。前半部分所有元素都应小于后半部分,前<base,后>base。
            int partition = partitionLR(data,left,right);
            //分割后,就可以进行递归
            quickSort(data,left,partition-1);
            quickSort(data,partition+1,right);
        }

    }

    //实现数组分割功能
    public static int partitionLR(int[] data, int left, int right){
        int i = left;
        int j = right +1;
        int base = data[left];//基准值,默认是被分割的数组的第一个元素

        //取出数组第一个,中间,最后,三个数据,取大小位于中间的那个数来做base
        //上面的方法暗含一个前提:数组的总的数据项必须大于等于3个
        int size = (right-left) +1; //分割的数组的长度
        if (size >= 3){
            base = getMid(data,left,right);//实现三项取中。这个方法会把最大的数放到right位置,中值放在left位置。
        }

        while (true){
            while (i<right && data[++i]<base){}
            while (j>left && data[--j]>base){}

            if (i>=j){//如果已经遍历了整个数组,就跳出循环
                break;
            }else {//交换两个元素
                swap(data,i,j);
            }
        }

        //将base处元素和j处元素交换
        swap(data,left,j);
        return j;
    }

    //实现数组元素交换的方法
    public static void swap(int[] data, int i, int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    //三项取中的方法
    public static int getMid(int[] data, int left, int right){
        //数组中间的元素对应的索引
        int mid = (left+right)/2;

        //将三个数中最大的值放到最右面
        if(data[left] > data[right]){
            swap(data,left,right);
        }
        if(data[mid]>data[right]){
            swap(data,mid,right);
        }

        //将中指的数放到最前面
        if (data[left]<data[mid]){
            swap(data,left,mid);
        }

        return data[left];
    }

}

处理小划分

如果使用三数据取中划分方法,则必须遵循快速排序算法不能执行三个或者少于三个的数据,如果大量的子数组都小于3个,那么使用快速排序是比较耗时的。这个时候可以结合前面我们讲过简单的排序(冒泡、选择、插入)组合起来用。

当数组长度小于M时(hight-low <= M),不进行快排,而进行插入排序。转换参数M的最佳值和系统是相关的,一般来说,5-15之间的任意值在多说情况下都能令人满意

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值