排序之快速排序詳解
一、演算法介紹
快速排序(Quick Sort):它的基本思想是,通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,分別對這丙部分繼續進行快速排序,直至整個序列有序。
任取一個元素 (如第一個) 為中心
所有比它小的元素一律前放,比它大的元素一律後放,形成左右兩個子表;
對各子表重新選擇中心元素並依此規則調整,直到每個子表的元素只剩一個
①每一趟的子表的形成是採用從兩頭向中間交替式逼近法;
②由於每趟中對各子表的操作都相似,可採用遞迴演算法。
二、基本步驟
設定兩個指標i,j,首先在序列裡面選出一個樞紐temp出來,將j指向的數字和temp比較,如果比temp大,則減1,如果比temp小,應該把當前j指向的位置上面的數值和

三、演算法分析
最好:劃分後,左側右側子序列的長度相同,
最壞:從小到大排好序,遞迴樹成為單支樹,每次劃分只得到一個比上一次少一個物件的子序列,必須經過 n-1 趟才能把所有物件定位,而且第 i 趟需要經過 n-i 次關鍵碼比較才能找到第 i 個物件的安放位置
若出現各種可能排列的概率相同,則可取最好情況和最壞情況的平均情況
時間效率:O(nlog2n) —每趟確定的元素呈指數增加
空間效率:O(log2n)—遞迴要用到棧空間
穩 定 性: 不穩定 —可選任一元素為支點
1.如何選樞紐
由上述描述可以知道,快速排序是以樞紐的點進行來回交換,所以快速排序的排序趟數和初始的序列有關係。
所以選擇快速排序的樞紐點是非常重要的,因為關係到排序的效率。
取前或後法:序列中的第一個或最後一個元素作為基準,如果輸入序列(上文中的陣列)是隨機的,處理時間可以接受的。如果陣列已經有序時,此時的分割就是一個非常不好的分割。因為每次劃分只能使待排序序列減一,此時為最壞情況,時間複雜度為Θ(n^2)。而且,輸入的資料是有序或部分有序的情況是相當常見的。因此,使用第一個元素作為樞紐元是非常糟糕的
隨機選取基準:
這是一種相對安全的策略。由於樞軸的位置是隨機的,那麼產生的分割也不會總是會出現劣質的分割。在整個陣列數字全相等時,仍然是最壞情況,時間複雜度是O(n2)。所以隨機化快速排序可以對於絕大多數輸入資料達到O(nlogn)的期望時間複雜度。
三數取中法:在快排的過程中,每一次我們要取一個元素作為樞紐值,以這個數字來將序列劃分為兩部分。在此我們採用三數取中法,也就是取左端、中間、右端三個數,然後進行排序,將中間數作為樞紐值。顯然使用三數中值分割法消除了預排序輸入的不好情形,並且減少快排大約14%的比較次數。
2.如何證明時間複雜度
1、最優情況
在最優情況下,Partition每次都劃分得很均勻,如果排序n個關鍵字,其遞迴樹的深度就為 [log2n]+1( [x] 表示不大於 x 的最大整數),即僅需遞迴 log2n 次,需要時間為T(n)的話,第一次Partiation應該是需要對整個陣列掃描一遍,做n次比較。然後,獲得的樞軸將陣列一分為二,那麼各自還需要T(n/2)的時間(注意是最好情況,所以平分兩半)。於是不斷地劃分下去,就有了下面的不等式推斷:
這說明,在最優的情況下,快速排序演算法的時間複雜度為O(nlogn)。
2.最壞情況
然後再來看最糟糕情況下的快排,當待排序的序列為正序或逆序排列時,且每次劃分只得到一個比上一次劃分少一個記錄的子序列,注意另一個為空。如果遞迴樹畫出來,它就是一棵斜樹。此時需要執行n‐1次遞迴呼叫,且第i次劃分需要經過n‐i次關鍵字的比較才能找到第i個記錄,也就是樞軸的位置,因此比較次數為n(n-1)/2,最終其時間複雜度為O(n^2)。
3.平均時間複雜度
直接設對規模的陣列排序需要的時間期望為, 期望其實就是平均複雜度換個說法.
空表的時候不用排, 所以初值條件就是 T(0) = 0 .所謂快排就是隨便取出一個數,一般是第一個數,然後小於等於他的放左邊, 大於他的的排右邊.比如左邊 k 個那接下來還要排: T(n - k) + T (k - 1) 的時間.然後 k 多少那是不確定的, 遍歷 1~ n , 出現概率都是相等的. 另外分割操作本身也要時間 P(n) , 操作花費是線性時間 P(n) = cn , 這也要加進去, 所以一共是:
四、完整程式碼示例
public class QuickSort { //任取一個元素 (如第一個) 為中心 //所有比它小的元素一律前放,比它大的元素一律後放,形成左右兩個子表; //對各子表重新選擇中心元素並依此規則調整,直到每個子表的元素只剩一個 //一趟排序過程後我們返回樞紐的位置 int partition(int A[], int left, int right) { //選擇樞紐元素 int p = A[left]; while (left < right) { //如果尾指標位置的數比樞紐數要大,移動尾指標的位置,否則就把所指示的值給首指標的位置 while (left < right && A[right] >= p) { --right; } A[left] = A[right]; //如果首指標位置的數比樞紐數要小,移動首指標的位置,否則就把所指示的值給尾指標的位置 while (left < right && A[left] <= p) { ++left; } A[right] = A[left]; } //此時的首尾指標已經相等,把樞紐的值賦給首尾指標相等的位置即可 A[left] = p; return left; } //快速排序的遞迴 void Quick(int A[], int left, int right) { //定義一個樞紐的位置 int pnode; if (left < right) { pnode = partition(A, left, right); Quick(A, left, pnode - 1); Quick(A, pnode + 1, right); } } public static void main(String[] args) { }