快速排序详解

一、hoare法

1、思路

Ⅰ:通过比较将第一个值找到适合的地方放,同时将将这个值的左边比他小,右边比他大

Ⅱ:再通过递归将左边的值也排有序,左边排完排右边

 

 

下面开始代码实现

第一部分:设置好递归的结束条件,获取中间值,递归左边,再递归右边

    public static void quick(int[] array,int start, int end){
        if(start >= end){
            return;
        }
        //求基准,也是中兴值
        int pivot = parttion(array,start,end);
        //得到中间值再来递归排左树
        quick(array,start,pivot-1);
        //再来递归排右树
        quick(array,pivot+1,end);
    }

第二部分:求中间值

    private static int parttion(int[] array, int left,int right){
        //用index来记录left的下标(左树可以用0,但是到右树就不能用0了,所以需要一个值来记住)
        int index = left;
        //再定义一个tmp来记住left下标的值,方便进行比较
        int tmp = array[left];
        //当left >= right时表示相遇,可以退出
        while(left < right){
            //(为什么right要先走,left后走?)
            //往左走找比tmp小的值,如果大于等于就right--
            while(left < right && array[right] >= tmp){
                right--;
            }
            //往右走找比tmp大的值,如果小于等于就left++
            while(left < right && array[left] <= tmp){
                left++;
            }
            //此时left指向的值大于tmp,right指向的值小于tmp,将他们进行交换
            swap(array,left,right);
        }
        //此时退出循环表示left和right相遇了,再将left指向的值和index指向的值进行交换,使得index左边的值比他小,右边比他大
        swap(array,index,left);
        //最后返回left,也就是中间值的下标
        return left;
    }

    //交换方法
    public static void swap(int[]array, int left, int right){
        int tmp = array[left];
        array[left] = array[right];
        array[right] = tmp;
    }

这里有个小问题:为什么要右边先走而不是左边先走呢?

 

二、挖坑法

大体思路与hoare法差不多,也是找中间值,递归左边,再递归右边,不过找中间值的方法不一样
 

hoare是先找到大的,再找到小的,再交换,挖坑法是将0下标的值存入tmp,然后在右边找到比他
大的填入0下标,再从左边找到比他小的填入之前填到0下标的空

如图:

 

代码实现:第一部分与hoare一样

    private static int parttion(int[] array, int left, int right){
        //先将left位置的值存到tmp
        int tmp = array[left];
        while(left < right){
            //往左走找比tmp小的值,如果大于等于就right--
            while(left < right && array[right] >= tmp) {
                right--;
            }
            //找到后直接将right下标的值放到left的位置
            array[left] = array[right];
            //往右走找比tmp大的值,如果小于等于就left++
            while(left < right && array[left] <= tmp){
                left++;
            }
            //找到后直接将left下标的值放到right下标的位置
            array[right] = array[left];
        }
        //退出表示left 和right 相遇,将开始存的tmp的值放到left/right
        array[left] = tmp;
        //最后返回left(中间值下标
        // .)
        return left;
    }

三、双指针法

图可能有点长,讲有点抽象,建议结合代码一起看 

 

代码图:

 

在传参时传入了left值,就不用使用index来记录了,直接使用left就行

    private static int parttion(int[] array, int left, int right){
        //把left的位置给prev,也就是初始位置
        int prev = left;
        //cur为初始位置+1
        int cur = left+1;
        //判断cur是否已经超过数组的长度,也就是结束位置
        while(cur <= right){
            //如果cur处的值比left(初始位置)的值小
            //并且prev+1位置的值不是cur,就进行交换
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                /**
                 * 比如6 1 2 7 9 3 4 5 10 8,此时的prev是6,cur是1
                 * cur小于left成立,执行后面的表达式++prev,也就是此时prev的值为1 ,与cur相等,结果为假,cur++
                 * 此时的prev是1,cur是2,一直到7cur < left 不成立 ,那么prev不会执行了,也就是不会在++
                 * cur会一直加到3,此时的cur < left成立,执行后面的表达式,++prev的值为7,cur的值为3,条件成立,进行交换,cur++
                 * 此时数组为6 1 2 3 9 7 4 5 10 8,prev指向3,cur指向4
                 * 继续判断4 < 6 成立,++prev为9,cur为4,成立,4和9进行交换
                 * 6 1 2 3 4 7 9 5 10 8一直这样继续下去直到cur>right停止
                 */
                //总的来说就是prev在比left下标大的值前面停下,cur在比left小的值停下,在判断prev后面的这个值是否与cur相等,相等不交换
                QuickHoare.swap(array,cur,prev);
            }
            cur++;
        }
        QuickHoare.swap(array,prev,left);
        return prev;
    }

四:三数取中法(也可以认为是优化)

为什么要进行优化呢?

快速排序在好的情况下复杂度是O(n*log N),坏的情况就是O(N^2)层数太多,

 

那么三数取中是怎么做的呢?

即知道这组无序数列的首和尾后,我们便可以求出这个无序数列的中间位置的数,我们只需要在首,中,尾这三个数据中,选择一个排在中间的数据作为基准值,进行快速排序,即可进一步提高快速排序的效率(在首尾中三个数据中,找到一个中间值,然后将这个中间值放到数组的首位置,那么排序就会按照这个轴来排,就可以提高效率

首先我们已知首(left)尾(right),那么中间值就是mid(left+right) / 2,

那么怎么取中间值呢?

六种情况  假设三个数为 l   r   m

那么只有   l < r < m -->  l < m< r --> m < l < r  (这是 l  小于 r的情况)

                 r < l < m --> r < m < r --> m < r < l  (这是 l 大于 r 的情况)

代码实现:

    public static int threeNum(int[] array, int left, int right) {
        //先找到left和right的中间值
        int mid = (left + right) / 2;
        //再比较left和right谁的值比较大
        if (array[left] > array[right]) {
            //进入说明l大于f,再去判断中间值在哪
            if (array[mid] > array[left]) {
                //如果mid(中间值)大于left,说明mid 》 left 》 right,此时的中间值是left
                return left;
            } else if (array[mid] < array[right]) {
                //如果mid小于right 说明left 》 right 》 mid,此时中间值为right
                return right;
            } else {
                //两种情况都不是说明left 》 mid 》 right
                return mid;
            }
        } else {
            //如上
            if (array[mid] > array[right]) {
                return right;
            } else if (array[mid] < array[left]) {
                return left;
            } else {
                return mid;
            }
        }

三数取中的方法写完,我们就可以在快排方法中进行调用,用index来接收,再将index和首位进行交换

然后再进行快排(这里选择挖坑法进行快排)

    private static void quick(int[] array, int start, int end) {
        //等于表示只有一个值了,所以不用再排了·
        if (start >= end) {
            return;
        }
        //三数取中
        int index = threeNum(array, start, end);
        //交换
        QuickHoare.swap(array, start, index);
        //找基准值
        int flag = prattion(array, start, end);

        quick(array, start, flag - 1);
        quick(array, flag + 1, end);
    }

这里三数取中优化就完成了,其实还能再进行优化,插入排序是越有序越快

在快排到最后一层的时候数据太多了,那么我们可以给定一个区间,在这个区间内直接进行插入排序

    private static void quick(int[] array, int start, int end) {
        //等于表示只有一个值了,所以不用再排了·
        if (start >= end) {
            return;
        }
        //选择一个范围进行插入排序,因为数据太大的话底层递归太多了,越往后面数据越有序,
        //左闭右闭区间   1   2   3   三个数    假设下标为0   1   2    就是2 - 0 + 1 = 3(个数)不加1就少一个
        if(end - start + 1 <= 20){
            insertSort(array,start,end);
            return;
        }
        //三数取中
        int index = threeNum(array, start, end);
        //交换
        QuickHoare.swap(array, start, index);
        //找基准值
        int flag = prattion(array, start, end);

        quick(array, start, flag - 1);
        quick(array, flag + 1, end);
    }

插入排序代码:

    public static void insertSort(int[] array, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int tmp = array[i];
            int j = i - 1;
            for (; j >= left; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {
                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }

五:非递归快排

上面讲的都是使用递归来进行排序,如果数据太大的话,底层占用的内存太多,是会栈溢出的。

下面是非递归实现快排思路:

 后面的就直接来实现代码:

    private static void quick(int[] array){
        //创建一个栈来保存左右下标
        Stack<Integer> stack = new Stack<>();
        //首下标为0
        int start = 0;
        //尾下标尾长度-1
        int end = array.length-1;
        //求中间值
        int index = parttion(array,start,end);
        //判断中间值左右是否有两个数
        if(index -1 > start){
            stack.push(start);
            stack.push(index-1);
        }
        if(end > index+1){
            stack.push(index+1);
            stack.push(end);
        }
        //进入循环,判断栈是否为空
        while(!stack.isEmpty()){
            //弹出的元素先给右边的尾
            end = stack.pop();
            //再给右边的首
            start = stack.pop();
            //求中间值
            index = parttion(array,start,end);
            if(index  > start+1){
                stack.push(start);
                stack.push(index-1);
            }
            if(end > index+1){
                stack.push(index+1);
                stack.push(end);
            }
        }
    }


    private static int parttion(int[] array, int left, int right){
        //先将left位置的值存到tmp
        int tmp = array[left];
        while(left < right){
            //往左走找比tmp小的值,如果大于等于就right--
            while(left < right && array[right] >= tmp) {
                right--;
            }
            //找到后直接将right下标的值放到left的位置
            array[left] = array[right];
            //往右走找比tmp大的值,如果小于等于就left++
            while(left < right && array[left] <= tmp){
                left++;
            }
            //找到后直接将left下标的值放到right下标的位置
            array[right] = array[left];
        }
        //退出表示left 和right 相遇,将开始存的tmp的值放到left/right
        array[left] = tmp;
        //最后返回left(中间值下标)
        return left;
    }

我们也可以将三数取中优化加入到循环当中来

    private static int parttion(int[] array, int left, int right){
        //先将left位置的值存到tmp
        int tmp = array[left];
        while(left < right){
            //往左走找比tmp小的值,如果大于等于就right--
            while(left < right && array[right] >= tmp) {
                right--;
            }
            //找到后直接将right下标的值放到left的位置
            array[left] = array[right];
            //往右走找比tmp大的值,如果小于等于就left++
            while(left < right && array[left] <= tmp){
                left++;
            }
            //找到后直接将left下标的值放到right下标的位置
            array[right] = array[left];
        }
        //退出表示left 和right 相遇,将开始存的tmp的值放到left/right
        array[left] = tmp;
        //最后返回left(中间值下标)
        return left;
    }

    /**
     * 非递归快速排序
     * @param array
     * 思路:定义一个栈,在用start存起始位置,end存结束位置
     *      再使用parttion方法求出中间值
     *      再判断中间值左边是否有两个数 index-1 > start?  成立将start和index-1存入栈
     *      再判断中间值右边是否有两个数 index+1 < end?  成立将index+1 和 end存入栈
     *      循环判断栈是否为空
     *      不为空弹出栈的值,先给end,再给start(先进后出)
     *      再使用parttion方法求出中间值
     *      再判断中间值左边是否有两个数 index-1 > start?  成立将start和index-1存入栈
     *      再判断中间值右边是否有两个数 index+1 < end?  成立将index+1 和 end存入栈
     */
    private static void quick(int[] array){
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        //当区间范围不大时表示已经趋于有序,可以使用插入排序
//        if(end - start + 1 <= 20){
//            QuickThreeNum.insertSort(array,start,end);
//        }
//        //使用三数取中进行优化
//        int flag = QuickThreeNum.threeNum(array,start,end);
//        //三数取中完将中间值与start进行交换
//        QuickHoare.swap(array,0,flag);
        int index = parttion(array,start,end);
        if(index -1 > start){
            stack.push(start);
            stack.push(index-1);
        }
        if(end > index+1){
            stack.push(index+1);
            stack.push(end);
        }
        while(!stack.isEmpty()){
            end = stack.pop();
            start = stack.pop();
//            if(end - start + 1 <= 20){
//                QuickThreeNum.insertSort(array,start,end);
//            }
//            flag = QuickThreeNum.threeNum(array,start,end);
//
//            QuickHoare.swap(array,0,flag);
            index = parttion(array,start,end);
            if(index  > start+1){
                stack.push(start);
                stack.push(index-1);
            }
            if(end > index+1){
                stack.push(index+1);
                stack.push(end);
            }
        }
    }

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值