排序演算法之快速排序
阿新 • • 發佈:2020-09-07
一、演算法思想
快速排序(Quick Sort)使用分治法策略。它的基本思想是:選擇一個基準數,通過一趟排序將要排序的資料分割成獨立的兩部分;其中一部分的所有資料都比另外一部分的所有資料都要小。然後,再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
二、演算法流程
(1) 從數列中挑出一個基準值。
(2) 將所有比基準值小的擺放在基準前面,所有比基準值大的擺在基準的後面(相同的數可以到任一邊);在這個分割槽退出之後,該基準就處於數列的中間位置。
(3) 遞迴地把"基準值前面的子數列"和"基準值後面的子數列"進行排序。
三、演算法實現
1 //快速排序 2 static class QuickSort implements Sort { 3 @Override 4 public String sortName() { 5 return "快速排序"; 6 } 7 @Override 8 public Comparable[] sort(Comparable[] data) { 9 int lo = 0, hi = data.length - 1; 10 sort(data, lo, hi);11 return data; 12 } 13 //遞迴體 14 private static void sort(Comparable[] data, int lo, int hi) { 15 if(hi <= lo) return; 16 int partition = partition(data, lo, hi); 17 sort(data, lo, partition - 1); 18 sort(data, partition + 1, hi);19 } 20 //分組 21 private static int partition(Comparable[] data, int lo, int hi) { 22 //確定分界值 23 Comparable key = data[lo]; 24 //定義兩個指標,分別指向待切分元素的最小索引處和最大索引處的下一個位置 25 int left = lo, right = hi + 1; 26 //切分 27 while(true) { 28 //先從左往右掃描,移動right指標,找到一個比分界值小的元素,停止 29 while(!Sort.greater(key, data[--right])) { 30 if(right == lo) break; 31 } 32 //再從左往右掃描,移動left指標,找到一個比分界值大的元素,停止 33 while(!Sort.greater(data[++left], key)) { 34 if(left == hi) break; 35 } 36 //判斷left>=right,如果是,則證明元素掃描完畢,結束迴圈,如果不是,則交換元素即可 37 if(left >= right) { 38 break; 39 } else { 40 Sort.swap(data, left, right); 41 } 42 } 43 //交換分界值 44 Sort.swap(data, lo, right); 45 return right; 46 } 47 }
四、演算法分析
複雜度
因為快速排序有兩個主要操作,所以分析時間複雜度的時候咱們也是從這兩個操作著手。
首先是切割操作,對於長度為的佇列,切割操作有兩種可能:
- :不執行切割操作,所以時間複雜度為常數 1
- :執行切割操作,因為次比較,所以時間複雜度為
接著是遞迴操作,設對於長度為的佇列遞迴操作的時間複雜度為,執行切割操作後其中一個子佇列的長度為。
那麼根據遞迴操作的邏輯,咱們可以得出遞迴操作的時間複雜度計算公式:
分析完兩個主要操作的時間複雜度之後,咱們就可以開始從整體上分析快排的時間複雜度了。快排的時間複雜度分析分三種情況:最壞、最好和平均。
- 最壞:每次都只切割出一個子佇列,即。這時候每次遞迴操作的時間複雜度,所以對於長度為的佇列來說,快排最壞情況下的時間複雜度,即
- 最好:每次都對半切割,即,總共會執行次遞迴。這時候每次遞迴操作的時間複雜度,而,代入可得
……
所以最好情況下快排的時間複雜度為 - 平均:每次都隨機切割,即。這時候每次遞迴操作的時間複雜度
,最後得出時間複雜度約為。
穩定性
快速排序出於效能上的考慮,犧牲了演算法的穩定性。雖然可以改變交換規則,使得演算法保持穩定性,但卻犧牲了效能,對於一個排序演算法而言,這是得不償失的,除非是上下文有穩定性方面的需求,否則,不建議改變交換規則。