深入了解快排 以及 优化

快排


🎈🎆🎇 前言

快排可以说是在排序中的地位重中之重,不仅在七大排序中速度快,而且也是面试官经常考的排序,也是数据结构中必须掌握的排序

🍕🍔🍟快速排序:

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

🚗🚓🚕基本思想:

1、从待排序区间选择一个数作为基准值;

2、遍历这个待排序区间,比基准值小的放基准值得左边,比基准值大的放在右边;

3、采用分治思想,当left和right相遇的时候,就可以分为左右两个区间,

4、按照第2点同样的方式进行遍历,递归,直到各小区间长度==1,代表有序,长度是0,代表没有数据。

做题常见选基准值方法有:

  • 挖坑法;

  • Hoare 法;


下面找基准的方法是对快排的优化:

  • 随机选择;

  • 三数取中;

  • 直接插入排序

一、挖坑法

☮✝☪思路:

最开始设start为基准值,放在tmp中,然后比较大小,比基准值小的放基准值得左边,比基准值大的放在右边,如果end比tmp大就–,直到找到比tmp小的值交换,start同理,比tmp小小就++,直到找到比tmp大的值交换。

image-20211029214645929

🍳🧇🥞代码:

public static int partition(int[] array,int start,int end) {
        int tmp=array[start];
        while(start<end) {
            //start<end 防止有序数组的时候end超过start  例如:1 2 3 4 5 6
            while(start<end && array[end]>=tmp) {//必须取等号,不然会无限循环
                end--;
            }
            array[start]=array[end];
            while (start<end && array[start]<=tmp) {
                start++;
            }
            array[end]=array[start];
        }
    //最后start和end相遇,使数组一分为二,完成分治思想
        array[start]=tmp;
        return start;
    }

image-20211029220854160

注意,一定要加上“=”不然会死循环

🍖🍗🥩完整代码:

public static int partition(int[] array,int start,int end) {
        int tmp=array[start];
        while(start<end) {
            while(start<end && array[end]>=tmp) {
                end--;
            }
            array[start]=array[end];
            while (start<end && array[start]<=tmp) {
                start++;
            }
            array[end]=array[start];
        }
        array[start]=tmp;
        return start;
    }

    //待排序区间
    public static void quick(int[] array,int left,int right) {
        if(left>=right) {
            return;
        }
        int pivot=partition(array,left,right);
        //递归 直到各小区间长度==1,代表有序,长度是0,代表没有数据。
        quick(array,left,pivot-1);
        quick(array,pivot+1,right);
    }

    public static void quickSo(int[] array) {
        quick(array,0,array.length-1);
    }

1.1 性能

时间复杂度:O(n*logn)

空间复杂度:O(logn)

稳定些:不稳定

二、Hoare法:

Hoare法思想其实跟挖坑法没有什么区别,都很好理解,

🍘🍙🍚思路:

最开始设start为基准值,放在tmp中,然后比较大小,同时找到last比基准值小的,begin比基准值大,他两进行交换,最后begin和last相遇的值再跟start进行交换。

image-20211030163142182

🧚‍♀️🧚‍♂️🤹‍♀️核心代码:

 public static int parentition2(int[] array,int start,int end) {
        int begin=start;
        int last=end;
        int tmp=array[start];
        while (begin<last) {
            while (begin<last && array[last]>=tmp) {
                last--;
            }
            while (begin<last && array[begin]<=tmp) {
                begin++;
            }
            swap(array,begin,last);
        }
        swap(array,begin,start);
        return begin;
    }

一般情况下,做题通常用的是挖坑法,例如选择题;

那么当数据有序的情况下,快速排序的时间复杂度会变成O(n^2),空间复杂度O(n),这也是最坏的情况下,所以引出了快速排序的优化。

三、三数取中法

快速排序的思想就是分而治制,在有序的数据中如果每次将待排序的的序列均匀的分割,时间复杂度就会更快;

🦪🍣🍤三数取中思路:

取中就是这一组数据中找到中间值,使中间值作为基准,进行分治;

条件为array[mid] <= array[left] <= array[right]

🍰🎂🍪核心代码:

public static void medianOfThree (int[] array,int left,int right) {
        int mid=(left+right)/2;
        if(array[mid]>array[left]) {
            int tmp=array[mid];
            array[mid]=array[left];
            array[left]=tmp;
        }
        if(array[left]>array[right]) {
            int tmp=array[left];
            array[left]=array[right];
            array[right]=tmp;

        }
        if(array[mid]>array[right]) {
            int tmp=array[mid];
            array[mid]=array[right];
            array[right]=tmp;
        }
    }

四、 随机选择

也是为了防止有序数组,采用在待排序列中任意取里面的元素作为基准;

🍵🧉🍶核心代码:

 public static void pivotRandow(int[] array,int left,int right) {
        //随机选择法
        Random random=new Random();
        int rand=random.nextInt(right-left)+left+1;
        int tmp=array[left];
        array[left]=array[rand];
        array[rand]=tmp;
    }

五、直接插入排序优化

一组数据越有序,那么直接插入排序是最快的,快排递归到某一个区间的时候会逐渐有序,我们就可以采用直接插入排序来优化

public static void quick(int[] array,int left,int right) {
        if(left>=right) {
            return;
        }
        int pivot=parentition2(array,left,right);

        //执行到一个区间之后  进行直接插入排序会更快
        if((right - left + 1) <= 100) {
            insertSort(array,left,right);
            return;
        }
        medianOfThree(array,left,right);
        //递归
        quick(array,left,pivot-1);
        quick(array,pivot+1,right);
    }

六、非递归快排

非递归快排比递归要麻烦点,分析的过程也要繁琐一点,但是有了前面挖坑法,Hoare法的基础也可以好理解;

🥂🥃🥤基本思路:

首先我们还是分治思想和前面的pivot一样,然后我们需要一个栈,pivot是一个基准,就各自分为两组待排序区间,在栈中放入开始和结束的下标,两个一组弹出,再跟上一步操作一样找基准点,相遇过后,又放入栈中,重复找基准点,直到完成排序

image-20211030212357930

需邀注意的点:

压栈的基准点左右两点是否有两个以上数据,所判断的公式是:pivot<right-1,不满足条件说明只有一个数据,就压栈前面的两个下标即可。左边的待排区间也是一样,基准的左边判断是否有两个数据的公式是:pivot>start+1。

👨‍🦲👩‍🦲👩‍🔬代码:

public static void quickSort(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        int pivot = partition(array,start,end);
        if(pivot > start+1) {//判断左边是否有两个数据
            stack.push(start);
            stack.push(pivot-1);
        }
        if(pivot < end-1) {//判断右边是否有两个数据
            stack.push(pivot+1);
            stack.push(end);
        }
        while (!stack.empty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            if(pivot > start+1) {
                stack.push(start);
                stack.push(pivot-1);
            }
            if(pivot < end-1) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }

以上就是快排最核心的点了,必须全掌握,若想看全代码可以 点击 Github ,


铁汁们,觉得笔者写的不错的可以点个赞哟❤🧡💛💚💙💜🤎🖤🤍💟,收藏关注呗,你们支持就是我写博客最大的动力

image-20211030200313412

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸢也

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值