快速排序(如下简称“快排”)的核心思想是分治法。能够说,分治提供了另外一种解决问题的思路。举个例子来进行说明,抓稳扶好,直接开车了……java
现有一个集合{4,8,2,5,7,-1,3}
,咱们将对它进行从小到大排序:segmentfault
1.选取第一个元素4
做为基准值,后面的元素逐个和这个基准值比较大小函数
显然,要么大于,要么小于( 暂不考虑相等的状况)。编码
2.按比较大小结果进行安置:大于基准值的元素,置于它的左侧;小于基准值的元素,置于它的右侧(若是有相等状况,左右随意)spa
为好理解,画出了数轴,将"基准值4"做为中轴线。第二个元素8大于4,放于右侧;第三个元素2小于4,放于左侧……以此类推,最后一个元素放置完毕后是这样的code
3.“重复”。先抛开右侧的big
集合,专一于左侧集合{2,-1,3}
。此时,咱们把它看成source,重复第一步的操做。排序
如此重复下去,直到只剩下一个元素的状况。递归
此时从左到右读出图中曾做为基准值的元素(菱形)——-1,2,3,4
,咱们发现已经排序好了。最后给出完整的操做示意图:索引
每次根据条件划分红两部分,划分后每部分分别治理,即分治。ip
上面的例子中,第三步叫“重复”其实并不许确,真正的名字是递归。
每一个递归函数都有两部分:
拿上一篇文章(【算】选择排序和二分查找)里聊过的二分查找举例:
/** * 二分查找,递归版 * @param target * @param source * @return */ public static int doFind(int target,List<PositionBean> source){ if(CollectionUtils.isEmpty(source)){ throw new RuntimeException("集合为空"); } int halfIndex = source.size()/2; int halfElement = source.get(halfIndex).getVal(); if(target==halfElement){ //基线条件:若是目标target和中间数相等,bingo!找到了,返回索引 return source.get(halfIndex).getIndex(); }else if(source.size()==1){ //另外一个基线条件:就剩下最后一个元素了,仍然与目标target不符,则返回“目标不存在” throw new RuntimeException("目标不存在"); }else{ //递归条件:选取符合条件的那一半集合,递归调用本身 if (target < halfElement) { List<PositionBean> tempSource = source.subList(0, halfIndex); return doFind(target, tempSource); } else { List<PositionBean> tempSource = source.subList(halfIndex, source.size()); return doFind(target, tempSource); } } }
掌握了递归和分治思想后,快速排序就只剩下编码部分了:
/** * 快排 * @param source * @return */ public static List<Integer> doOrder(List<Integer> source){ if(source.size()<=1){ //基线条件 return source; } int temp = source.get(0); List<Integer> lowElements = new LinkedList<>(); List<Integer> highElements = new LinkedList<>(); for(int i=1,len=source.size();i<len;i++){ int element = source.get(i); if(element<=temp){ lowElements.add(element); }else{ highElements.add(element); } } lowElements = doOrder(lowElements); //递归条件 highElements = doOrder(highElements); //递归条件 List<Integer> res = new LinkedList<>(lowElements); res.add(temp); res.addAll(highElements); return res; }
快排的平均时间复杂度为O(n log n)