快速排序(如下簡稱「快排」)的核心思想是分治法。能夠說,分治提供了另外一種解決問題的思路。舉個例子來進行說明,抓穩扶好,直接開車了……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)