快速排序,你值得拥有!

Java 快速排序


算法介绍
  • 一种很快的排序和其名字一样

  • 和希尔排序的思想类似,每次排序都是对整体进行相对有序化,着手处理整体的顺序.

  • 将序列分为无数个小序列,最终每个小序列都相对有序

实现步骤
  • 寻找一个基准值,然后比基准值小的放到基准值的左边,比基准值大的放到基准值的右边
  • 执行完上述操作之后,我们以基准值进行分割,将数组分为两部分。
  • 每一个部分都当成一个独立的数组,分别找出基准值,都执行上述操作操作
  • 重复流程直至序列有序

看上去不难不难理解,我们给出一个图例

1.整理序列

如图所示的序列,我们找一个基准值,比如array.length/2也就是图中的0的位置。

image-20200827222838398

接下来我们维护两个指针,分别指向数组的一位和最后一位。

image-20200827223020267

如果left大于0,说明我们准备要把left所指向的值放到基准值的右边,这时我们在基准值右边的一个数字,来与之交换,我们判断right指向的是不是小于0,只要不小于0,我们让指针左移。

image-20200827223252977

image-20200827223348617

我们之前的流程是先找left找到了再去找right,所以这里我们继续寻找比0大的,移动left指针

image-20200827223524401

这时我们找到了7大于0,我们移动right,我们发现right的位置以及到达了base的位置,而且我们right是为了表示base右边的值,如果我们的right可以跑到base左边显然不合理。
在这里插入图片描述

这里我们有个操作,交换一下left和base,因为right既然能到达base的地方,说明base的右边已经排好了,但是left的位置还没有排好,但是left的左边也是排好的,所以我们这个操作的目的是让漏掉的元素都能参与排序

image-20200827223856584

第一步结束了,我们执行第二步

2.分割序列

按照算法的要求我们按照基准值分割序列,我们按照基准值0进行分割序列

image-20200827225413940

每一个子序列都进行快速排序

左边的基准值为-2,右边的为1,最终排序结果

image-20200827225454231

再分割,由于-2是基准,-2的右边为空,左边有一个,则右边没法分,左边分来

image-20200827225523167

排序image-20200827225544878

分割,当所有的子序列长度都是1,则停止排序

image-20200827225720688

通过上述流程我们得到几个规律

  • 当子序列长度为2时,我们拆分之后的部分无需再排序了
  • 如果基准值在头部或者尾部,比如在头部,那么左边没有序列不需要分割,右边进行分割
  • 基准不需要再进行排序,因为基准的左边小于基准了,右边大于基准,只要左右都排好了,基准的顺序自然正确
1.整理(代码实现)
public class QuickSort implements SortAlgorithm {

    @Override
    public void sort(int[] array) {
        //第一次排序
        int left=0;
        int right=array.length-1;
        doQuickSort(array,left,right);
    }

    public void doQuickSort(int[] array,int start,int end){
        int left=start;
        int right=end;
        //计算基准值
        int base=(right+left)/2;
        while (left<right){
            //直到left=base或者找到一个比基准值大的
            while (left<base && array[left]<=array[base]){
                left++;
            }
            //直到base=right或者找到一个比基准值小的
            while (right>base && array[right]>=array[base]){
                right--;
            }
            //判断是否达到了基准值,如果达到,下次交换操作的时候基准值也应该相应的跟着变
            if (base==left){
                base=right;
            }
            else if (base==right){
                base=left;
            }
            else{
                base=base;
            }
            //交换操作
            //这个方法是SortAlgorithm接口的default方法,就是简单的交换操作
            reverse(array,left,right);
        }
    }
}

测试代码

//测试数组
int[] ints = {12,76,23,11,47,98,23,75,23};
new QuickSort().sort(ints);
System.out.println(Arrays.toString(ints));

运行结果

我们看到比47小的在47左边,比47大的在47右边

[12, 23, 23, 11, 23, 47, 98, 75, 76]
2.拆分(代码实现)

我们知道有几种特殊情况

假设我们的序列为array,基准值为base,序列的的最左边的元素的下标为left,最右边为left,那么我们的子序列array1为{left,base-1},array

2为{base+1,right};

如果base+1=right,或者base+1>right这种子序列直接卡掉

更新代码

    public void doQuickSort(int[] array,int start,int end){
        int left=start;
        int right=end;
        //程序出口
        if (left>=right){
            return;
        }
        int base=(right+left)/2;
        while (left<right){
            while (left<base && array[left]<=array[base]){
                left++;
            }
            while (right>base && array[right]>=array[base]){
                right--;
            }
            if (base==left){
                base=right;
            }
            else if (base==right){
                base=left;
            }
            else{
                base=base;
            }
            reverse(array,left,right);
        }
        //计算子序列
        doQuickSort(array,start,base-1);
        doQuickSort(array,base+1,end);
    }
}

运行结果

[11, 12, 23, 23, 23, 47, 75, 76, 98]

我们的排序算法写好了,但是我们折中的方式,有一定的不好理解,这里我们再给出一种基准值的思路

方式2

我们把第一位作为基准值,然后定义一个变量保存这个值,依旧维护两个指标left和right。

image-20200827235504999

我们的基准值为第一个元素,我们从right开始,寻找比1小的数字,我们找到了-2

image-20200827235628521

接下来的操作很厉害,就是-2去覆盖1,也就是right覆盖left,然后right指针不动,改为移动left,寻找比1要小的值,刚好-5就是了!

image-20200827235737791

-5覆盖-2,也就是left覆盖right,right开始移动,按照如上流程我们快速的运行一遍,当我们left>=right作为循环的出口,最后我们还要把基准值加入到left=right的位置

代码实现

  • 如何在覆盖之后改为让另一个指针运行起来?

直接上代码

public class QuickSortPro implements SortAlgorithm {
    @Override
    public void sort(int[] array) {
        recursive(array,0,array.length-1);
    }


    public void recursive(int[] array,int left,int right){
		//程序出口
        if(left>=right){
            return;
        }
        int start=left;
        int end=right;
        int tmp=array[start];
        //通过维护一个flag标记来实现交替操作
        boolean flag=true;
        while(start<end){
            if(flag){
                if(array[end]>tmp){
                    end--;
                }
                else {
                    array[start]=array[end];
                    start++;
                    flag= false;
                }

            }
            else {
                if (array[start]<tmp){
                    start++;
                }
                else {
                    array[end]=array[start];
                    end--;
                    flag=true;
                }
            }
        }
        //循环结束,此时start=end,我们用之前保存的基准值覆盖array[start]
        array[start]=tmp;
        recursive(array,left,start-1);
        recursive(array,start+1,right);
    }
} 

运行结果

[11, 12, 23, 23, 23, 47, 75, 76, 98]
总结
  • 学会选择基准值,让在基准值左右满足交换条件的元素不同的情况下,能对基准值的位置继续调整
  • 判断哪些子序列是没有意义的
  • 学会用boolean指针来实现一个交替操作的案例
彩蛋
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    //方式1的时间测试
    SortUtils.timeComplexityTest(QuickSort.class);
    //方式2时间测试
    SortUtils.timeComplexityTest(QuickSortPro.class);
}

运行结果

开始计算....
QuickSort:26ms
开始计算....
QuickSortPro:14ms

第二种覆盖的方式比折中的方式快了一倍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值