序言
快速排序,顧名思義就是具有較快的排序速度,它利用了跟歸併排序一樣的分治思想。它同時是一種原址性的排序方法,最好情況下的時間複雜度爲O(nlgn), 最壞情況下的時間複雜度爲O(n2)。
快速排序的描述
快速排序利用的是分治思想,既然用到的是分治思想,那麼對於A[p…r]自然就有分解,解決,合併的過程:
分解:數組A[p…r]被劃分成兩個子數組A[p…q-1]和A[q+1,r],使得A[p…q-1]中的每個元素都小於等於A[q],而A[q]小於等於A[q+1…r]中的元素,下標q也在這個劃分過程中進行計算。
解決:通過遞歸調用快速排序,對子數組A[p…q-1]和A[q+1…r]排序
合併:由於快排具有原址性,所以是不需要合併的。
對於快速排序的過程,在書中的源代碼是:
QUICKSORT(A,p,r)
if p > r
then q = PARTITION(A,p,r)
QUICKSORT(A,p,q-1)
QUICKSORT(A,q+1,r)
可以看出快排和前面章節介紹的歸併排序在算法上具有一定的相似性。主要的關鍵步驟在於PARTITION
函數,返回值是關鍵字的位置q。
對於數組的劃分(PARTITION),在書中的源代碼是:
PARTITION(A,p,r)
x = A[r]
i = p-1
for j = p to r-1
do if A[j] ≤ x
then i ++
swap(A[i],A[j])
swap(A[i+1],A[r])
return i+1
選取最後一個數作爲基準數,經過這樣的一趟劃分下來後,會使得A[q]的左邊的數都小於A[q],A[q]的右邊的數都大於A[q], 而A[q]恰好是這個基準數。具體過程如下圖:
快速排序算法的C語言實現
根據書中的源代碼,很容易用C語言寫出:
#include <stdio.h>
void QuickSort(int* A, int p, int r); //快速排序
int Partition(int* A, int p, int r); //數組的劃分
void swap(int* a, int* b); //兩數交換
void main()
{
int Array[8] = { 6,2,4,3,8,9,7,1 };
int p = 0;
int r = 7;
QuickSort(Array, p, r);
for (int k = 0; k < 8; k++)
printf("%d ", Array[k]);
printf("\n");
}
void QuickSort(int* A, int p, int r) //快速排序
{
int q;
if (p < r)
{
q = Partition(A, p, r);
QuickSort(A, p, q - 1); //遞歸調用
QuickSort(A, q + 1, r);
}
}
int Partition(int* A, int p, int r) //實現數組的劃分
{
int x, i, j, temp;
x = A[r]; //取最後一個數作爲基準數
i = p - 1;
for (j = p; j < r; j++) {
if (A[j] <= x)
{
i++;
swap(&A[i], &A[j]);
}
}
swap(&A[i + 1], &A[r]);
return i + 1;
}
void swap(int* a, int* b) { //交換a,b兩個數
int temp;
temp = *a;
*a = *b;
*b = temp;
}
快速排序的性能分析
快速排序的運行時間依賴於劃分是否平衡,平衡與否有依賴於劃分的元素。如果劃分是平衡的,快排的性能會和歸併排序是一樣的,如果不平衡的話,那麼快排就會接近於插入算法。
-
最壞情況劃分
在快排中,最壞的情況就是說對已經排好序的數組進行排序
。他的遞歸式是T(N) = T(N-1)+T(0)+O(N)。根據這個表達式我們可以得出所需要的時間是O(n2) -
最好情況劃分
平衡的劃分使得PARTITION過程得到的兩個子序列。這兩個子序列的規模都不超過n/2,這種情況下快排的性能是非常好的。他的遞歸式爲:T(N) = 2T(N/2) + O(N),由主方法可以得出,他的時間爲:Θ(nlgn)。 -
平衡的劃分
快速排序的平均情況運行時間與其最佳情況運行時間很接近,而不是非常接近於其最壞情況運行時間。例如:假設劃分過程總是產生9:1的劃分,算法的運行時間可表示爲:T(N) <= T(9N/10)+T(N/10)+cn。這一遞歸式對應的遞歸樹如圖:
在遞歸的每一層上都是9: 1的劃分,直觀上看起來非常不平衡,但快速排序的運行時間是O(nlgn),與恰好在中間劃分的漸近運行時間是一樣的。實際上,即使是99 : 1的劃分,其時間複雜度仍然是O(nlgn)。事實上,任何一種常數比例的劃分都會產生深度爲Θ(lgn)的遞歸樹,其中每一-層的時間代價都是O(n)。因此,只要劃分是常數比例的,算法的運行時間總是O(nlgn)。