快速排序的基本思想是,經過一輪的排序將序列分割成獨立的兩部分,其中一部分序列的關鍵字(這裏主要用值來表示)均比另外一部分關鍵字小。繼續對長度較短的序列進行一樣的分割,最後到達總體有序。在排序過程當中,因爲已經分開的兩部分的元素不須要進行比較,故減小了比較次數,下降了排序時間。html
詳細描述:首先在要排序的序列 a 中選取一箇中軸值,然後將序列分紅兩個部分,其中左邊的部分 b 中的元素均小於或者等於 中軸值,右邊的部分 c 的元素 均大於或者等於中軸值,然後經過遞歸調用快速排序的過程分別對兩個部分進行排序,最後將兩部分產生的結果合併便可獲得最後的排序序列。算法
「基準值」的選擇有不少種方法。最簡單的是使用第一個記錄的關鍵字值。可是若是輸入的數組是正序或者逆序的,就會將全部的記錄分到「基準值」的一邊。較好的方法是隨機選取「基準值」,這樣能夠減小原始輸入對排序形成的影響。可是隨機選取「基準值」的開銷大。數組
爲了實現一次劃分,咱們能夠從數組(假定數據是存在數組中)的兩端移動下標,必要時交換記錄,直到數組兩端的下標相遇爲止。爲此,咱們附設兩個指針(下角標)i 和 j, 經過 j 從當前序列的有段向左掃描,越過不小於基準值的記錄。當遇到小於基準值的記錄時,掃描中止。經過 i 從當前序列的左端向右掃描,越太小於基準值的記錄。當遇到不小於基準值的記錄時,掃描中止。交換兩個方向掃描中止的記錄 a[j] 與 a[i]。 而後,繼續掃描,直至 i 與 j 相遇爲止。掃描和交換的過程結束。這是 i 左邊的記錄的關鍵字值都小於基準值,右邊的記錄的關鍵字值都不小於基準值。數據結構
經過兩個不相鄰元素交換,能夠一次交換消除多個逆序,加快排序速度。快速排序方法在要排序的數據已經有序的狀況下最不利於發揮其長處。ide
下面咱們經過一個案例來演示一下快速排序的基本步驟: 以序列 46 30 82 90 56 17 95 15 共8個元素ui
初始狀態: 46 30 82 90 56 17 95 15 選擇46 做爲基準值,i = 0, j = 7spa
i = 0 j = 7指針
15 30 82 90 56 17 95 46 15 < 46, 交換 15 和 46,移動 i, i = 1code
i = 1 j = 7htm
15 30 82 90 56 17 95 46 30 < 46, 不須要交換,移動 i , i = 2
i = 2 j = 7
15 30 46 90 56 17 95 82 82 > 46, 交換82 和 46,移動 j , j = 6
i = 2 j = 6
15 30 46 90 56 17 95 82 95 > 46, 不須要交換,移動 j , j = 5
i = 2 j = 5
15 30 17 90 56 46 95 82 17 < 46, 交換46 和 17,移動 i, i = 3
i = 3 j = 5
15 30 17 46 56 90 95 82 90 > 46, 交換90 和 46,移動 j , j = 4
3 = i j = 4
15 30 17 46 56 90 95 82 56 > 46, 不須要交換,移動 j , j = 3
i = j = 3
i = j = 3, 這樣序列就這樣分割成了兩部分,左邊部分{15, 30, 17} 均小於 基準值(46);右邊部分 {56, 90,95,82},均大於基準值。這樣子咱們就達到了分割序列的目標。在接着對子序列用一樣的辦法進行分割,直至子序列不超過一個元素,那麼排序結束,整個序列處於有序狀態。
參考代碼:
1 #include <stdio.h> 2 3 #define MAX_NUM 80 4 5 void quicksort(int* a, int p,int q) 6 { 7 int i = p; 8 int j = q; 9 int temp = a[p]; 10 11 while(i < j) 12 { 13 // 越過不小於基準值的數據 14 while( a[j] >= temp && j > i ) j--; 15 16 if( j > i ) 17 { 18 a[i] = a[j]; 19 i++; 20 21 // 越太小於基準值的數據 22 while(a[i] <= temp && i < j ) i++; 23 if( i < j ) 24 { 25 a[j] = a[i]; 26 j--; 27 } 28 } 29 } 30 a[i] = temp; 31 32 for(int k = p; k <= q;k++) 33 { 34 if( k == i ) 35 { 36 printf("(%d) ",a[k]); 37 continue; 38 } 39 printf("%d ",a[k]); 40 } 41 printf("\n"); 42 43 if( p < (i-1)) quicksort(a,p,i-1); 44 if((j+1) < q ) quicksort(a,j+1,q); 45 } 46 47 int main(int argc, char* argv[]) 48 { 49 int a[MAX_NUM]; 50 int n; 51 52 printf("Input total numbers: "); 53 scanf("%d",&n); 54 55 if( n > MAX_NUM ) n = MAX_NUM; 56 57 for(int i = 0; i < n;i++) 58 { 59 scanf("%d",&a[i]); 60 } 61 62 printf("Divide sequence:\n"); 63 quicksort(a,0,n-1); 64 65 printf("The sorted result:\n"); 66 for(int i = 0; i < n;i++) 67 { 68 printf("%d ",a[i]); 69 } 70 printf("\n"); 71 72 73 74 return 0; 75 }
案例運行結果:(注:括號內表示的是基準值)
快速排序算法效率與穩定性分析
當基數值不能很好地分割數組,即基準值將數組分紅一個子數組中有一個記錄,而另外一個子組組有 n -1 個記錄時,下一次的子數組只比原來數組小 1,這是快速排序的最差的狀況。若是這種狀況發生在每次劃分過程當中,那麼快速排序就退化成了冒泡排序,其時間複雜度爲O(n2)。
若是基準值都能講數組分紅相等的兩部分,則出現快速排序的最佳狀況。在這種狀況下,咱們還要對每一個大小約爲 n/2 的兩個子數組進行排序。在一個大小爲 n 的記錄中肯定一個記錄的位置所須要的時間爲O(n)。若T(n)爲對n個記錄進行排序所須要的時間,則每當一個記錄獲得其正確位置,整組大體分紅兩個相等的兩部分時,咱們獲得快速排序算法的最佳時間複雜性。
T(n) <= cn + 2T(n/2) c是一個常數
<= cn + 2(cn/2+2T(n/4)) = 2cn+ 4T(n/4)
<= 2cn + 4(cn/4+ 2T(n/8)) = 3cn + 8T(n/8)
…… ……
<= cnlogn + nT(1) = O(nlogn) 其中cn 是一次劃分所用的時間,c是一個常數
最壞的狀況,每次劃分都獲得一個子序列,時間複雜度爲:
T(n) = cn + T(n-1)
= cn + c(n-1) + T(n - 2) = 2cn -c + T(n-2)
= 2cn -c + c(n - 2) + T(n-3) = 3cn -3c + T(n-3)
……
= c[n(n+1)/2-1] + T(1) = O(n2)
快速排序的時間複雜度在平均狀況下介於最佳與最差狀況之間,假設每一次分割時,基準值處於最終排序好的位置的機率是同樣的,基準值將數組分紅長度爲0 和 n-1,1 和 n-2,……的機率都是 1/n。在這種假設下,快速排序的平均時間複雜性爲:
T(n) = cn + 1/n(T(k)+ T(n-k-1)) T(0) = c, T(1) = c
這是一個遞推公式,T(k)和T(n-k-1)是指處理長度爲 k 和 n-k-1 數組是快速排序算法所花費的時間, 根據公式所推算出來的時間爲 O(nlogn)。所以快速排序的平均時間複雜性爲O(nlogn)。
快速排序須要棧空間來實現遞歸,若是數組按局等方式被分割時,則最大的遞歸深度爲 log n,須要的棧空間爲 O(log n)。最壞的狀況下在遞歸的每一級上,數組分割成長度爲0的左子數組和長度爲 n - 1 的右數組。這種狀況下,遞歸的深度就成爲 n,須要的棧空間爲 O(n)。
由於快速排序在進行交換時,只是根據比較基數值判斷是否交換,且不是相鄰元素來交換,在交換過程當中可能改變相同元素的順序,所以是一種不穩定的排序算法。
注:主要參考彭軍、向毅主編的 《數據結構與算法》