一、算法介紹
快速排序(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) {
}
參考文章
https://www.jianshu.com/p/c8b1384238f7
https://www.cnblogs.com/chengxiao/p/6262208.html
https://blog.csdn.net/oohaha_123/article/details/26558363
https://www.zhihu.com/question/22393997/answer/406278523
https://www.cnblogs.com/onepixel/articles/7674659.html
歡迎關注個人技術公眾號:Coder辰砂