【算法】排序01——從分治算法的角度理解快速排序(含代碼實現)

快速排序的時間複雜度爲 O[nlog (n)],空間複雜度是 O[log (n)] ,這種算法應用比較普遍(面試也愛考),很是適合在數據量較多且關鍵字隨機分佈的狀況下使用。java

記得博主第一次學習冒泡排序時以爲,這個冒泡排序很easy啊,而後就去看冒泡排序改進版的代碼(也就是快速排序啦),而後。。。。面試

 啊這 。。。算法

相信應該也有一些小夥伴和我過相似的經歷吧。即便能讀懂代碼,卻有難以理解其流程,面試手撕時也難以快速應對。數組

不事後來,當我接觸到分治算法後,再回過頭來這個快速排序的方法。。。。學習

就這?之後快排我不用提早準備,隨時手撕!測試

 好啦,廢話很少說了,進入正題。ui


 

先說一說什麼是分治算法:spa

就如字面意思,咱們須要把一個大的問題分解成多個形式相同的小問題解決,而且這些小問題的解的合併就是大問題的解。指針

這可能理解起來仍是抽象的,咱們就以排序舉例子:code

假設咱們的數組有這樣九個數字: [ 5 8 9 6 7 4 1 2 3 ] 

如今咱們從數組中隨便取一個數字(就去數組中的第一個數字吧),以它爲閾值,全部小於他數的放其左邊,並在邏輯上視爲一個新的數組(就叫小」數組「吧)。全部大於他的數放其右邊,一樣在邏輯上視爲一個新的數組(就叫大」數組「吧),而後咱們再對新出現的大數組和小數組進行一樣的操做直到新數組的大小小於2時中止:

以第一個數字5爲閾值進行拆分操做,分紅 [ 小數組 ] 閾值 [ 大數組 ] ,以下:

[ 4 1 2 3 ]  5  [ 8 9 6 7 ]         (在快速排序中小」數組「其實會是[ 3 2 1 4 ],由於快排會從原數組首尾的兩個指針向中間交替前進分大小,但這對咱們這個例子並沒有影響)

[ 1 2 3 ]  4 [ ]   5   [ 6 7 ] 8 [ 9 ]     黃色部分是對[4 1 2 3]的拆分操做,綠色部分是對 [8 9 6 7]的拆分操做,紅色部分要麼是當前操做的閾值要麼是長度不足2的數組,將不會參與後面的拆分操做

[ ] 1 [ 2 3 ] 4 [ ] 5  6 [ 7 ] 8 [ 9 ]     粉色部分是對[ 1 2 3 ]的拆分操做 ,淡藍色部分是對[ 6 7 ] 的拆分操做, 紅色部分要麼是當前操做的閾值要麼是長度不足2的數組,將不會參與後面的拆分操做

[ ] 1 2 [ 3 ] 4 [ ] 5 6 [ 7 ] 8 [ 9 ]      深藍色部分是對‘[ 2 3 ]的拆分操做,紅色部分要麼是當前拆分操做的閾值要麼是長度不足2的數組,將不會參與後面的拆分操做

如今你看,這個數組是否是有序了。

 

 

 

這就有點像創建一個二叉搜索樹同樣,把一個數組分紅一個閥值(父節點)、一個小數組(左子樹集合)和一個大數組(右子樹集合),而後對兩個新的數組(左、右子樹)遞歸該操做。

固然,快排和這個流程仍是有一點小區別的,當咱們把一個「數組」拆分紅兩個新「數組」時,上面的演示流程爲了方便你們理解,不憑空增長複雜性,採用的是從左向右遍歷原數組的每一個元素。

如 [ 5 8 9 6 7 4 1 2 3 ]  操做後的小數組順序是 [ 4 1 2 3 ]。但在快排中,對原數組的遍歷不是這樣的,它是使用兩個指針從原數組首尾開始向中間前進進行遍歷的(小夥伴能夠在下面的代碼裏體會),因此我纔在上面的小括號裏說快排中的小數組順序是[ 3 2 1 4 ]。(即尾指針向中間前進,遇到小於閾值5的數字的順序 3 2 1 4 )。

最後,你們從分治的思想來看看下面的快排代碼,是否是就不以爲更好理解了呢?

 1 import java.util.Arrays;  2 
 3 public class QuickSort {  4     public static void sort(int[] array,int begin_edge,int end_edge){  5         //左右邊界下標重合時,就表示邏輯上的新數組大小不足2了,  6         // 故再也不須要二分紅兩個新「數組」了
 7         if(begin_edge>=end_edge){return;}  8         //獲取新「數組」的首尾指針
 9         int l_pointer = begin_edge; 10         int r_pointer = end_edge; 11         int threshold = array[l_pointer]; 12         //循環結束時,全部l_pointer左邊的元素會小於threshold,右邊的會大於threshold(有點二叉搜索樹的意思2333)
13         while (l_pointer<r_pointer){//跳出循環時完成對當前「數組」的二分 14             //尾指針向前遍歷,遇到小於閾值的數就跳出循環,把它放進小「數組」
15             while (l_pointer<r_pointer && array[r_pointer]>=threshold){ 16                 r_pointer--; 17  } 18             array[l_pointer] = array[r_pointer]; 19             //首指針向後遍歷,遇到大於閾值的數就跳出循環,把它放進大「數組」
20             while (l_pointer<r_pointer && array[l_pointer]<=threshold) { 21                 l_pointer++; 22  } 23             array[r_pointer] = array[l_pointer]; 24  } 25         //把閾值插到兩個「數組」的中間以區分兩個「數組」的邊界(此時l_pointer等於r_pointer)
26         array[l_pointer] = threshold; 27         //遞歸調用。分治思想,化大問題爲形式相同的小問題
28         sort(array,begin_edge,l_pointer-1); 29         sort(array,l_pointer+1,end_edge); 30  } 31 
32     public static void main(String[] args) { 33         int array[] = { 4, 2, 5, 1, 7, 3, 5, 9, 8,1 }; 34         sort(array,0,array.length-1); 35  System.out.println(Arrays.toString(array)); 36  } 37 }

測試結果:

最後的最後,若是小夥伴以爲這篇博文對你有幫助的話,就長按👍。。。。啊,呸,就點個推薦吧

相關文章
相關標籤/搜索