前面講了插入排序、選擇排序、冒泡排序、歸併排序以及冒泡排序的改進版雞尾酒排序和插入排序的改進版希爾排序,下面來講一種很經常使用的排序方法:快速排序。html
快速排序既然敢以快速命名,能夠想見它的排序速度是很快的。事實也是如此,在實際應用中它的平均性能很是好,所以在通常狀況下屬於應用中的首選排序方式。web
快速排序與歸併排序同樣用了分治的思想。把一個要排序的數組按某個元素值(通常稱做主元)進行劃分,分紅兩邊,大於主元的放在一邊,小於的放在另外一邊,主元放兩邊的中間;而後再分別排序兩邊那兩個子數組,這樣就完成了排序。而那兩個子數組排序的時候一樣能夠用快速排序再分紅兩個子數組進行分別排序。以此類推,能夠一直分到分無可分,即數組中只有一個元素爲止。數組
從上面的過程能夠看出,快速排序是能夠在原址上排序的,因此不須要額外的空間進行歸併。其技巧在於如何按主元進行劃分子數組。app
劃分的過程有多種,這裏咱們選擇兩種常見的。svg
按照以上原理咱們來用代碼實現。函數
下面就是用C語言實現的代碼。分紅三個函數來實現。性能
void quick_sort(int a[], int n) { if (n<=0) return; quick_sort_(a, 0, n-1); } void quick_sort_(int a[], int low, int high) { if (low >= high) return; int pivot = 0; pivot = partition1(a, low, high); //pivot = partition2(a, low, high); quick_sort_(a, low, pivot-1); quick_sort_(a, pivot+1, high); } int partition1(int a[], int low, int high) { int x = a[low]; //取a[low]的值x做爲主元 /* 從右往左看,大於主元的保持在右邊區域,小於的放到左邊區域 */ int i = high + 1; //i指向右邊區域的最左邊 for (int j=high; j>low; j--) { //j指向左邊區域的最左邊 if (a[j] >= x) { //如有元素要放到右邊區域 i--; //從左邊區域的最右邊給騰出一個位置 swap(&a[i], &a[j]); //放入該元素,並把左邊區域的最右邊元素移到左邊 } // 區域的最左邊,此時j依然指向左邊區域的最左邊 } swap(&a[low], &a[i-1]); //將主元放到中間 return i-1; //返回主元所在位置下標 } int partition2(int a[], int low, int high) { int x = a[low]; //取a[low]的值x做爲主元 /* 把小於x的元素放到左邊區域,其他放到右邊區域,x放到中間 */ while (low < high) { while (low<high && a[high]>=x) high--; //從右往左找到第一個小於主元x的元素 a[low] = a[high]; //將其放到左邊區域的最右邊 while (low<high && a[low]<=x) low++; //從左往右找到第一個大於主元x的元素 a[high] = a[low]; //將其放到右邊區域的最左邊 } a[low] = x; //將主元放到中間 return low; //返回主元所在位置下標 } void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }
爲了驗證此函數的效果,加上了以下輔助代碼,對3個數組進行排序,運行結果在最後,可見排序成功。ui
#include <stdio.h> #include <stdlib.h> #define SIZE_ARRAY_1 5 #define SIZE_ARRAY_2 6 #define SIZE_ARRAY_3 20 void quick_sort(int a[], int n); void show_array(int a[], int n); void main() { int array1[SIZE_ARRAY_1]={1,4,2,-9,0}; int array2[SIZE_ARRAY_2]={10,5,2,1,9,2}; int array3[SIZE_ARRAY_3]; for(int i=0; i<SIZE_ARRAY_3; i++) { array3[i] = (int)((40.0*rand())/(RAND_MAX+1.0)-20); } printf("Before sort, "); show_array(array1, SIZE_ARRAY_1); quick_sort(array1, SIZE_ARRAY_1); printf("After sort, "); show_array(array1, SIZE_ARRAY_1); printf("Before sort, "); show_array(array2, SIZE_ARRAY_2); quick_sort(array2, SIZE_ARRAY_2); printf("After sort, "); show_array(array2, SIZE_ARRAY_2); printf("Before sort, "); show_array(array3, SIZE_ARRAY_3); quick_sort(array3, SIZE_ARRAY_3); printf("After sort, "); show_array(array3, SIZE_ARRAY_3); } void show_array(int a[], int n) { if(n>0) printf("This array has %d items: ", n); else printf("Error: array size should bigger than zero.\n"); for(int i=0; i<n; i++) { printf("%d ", a[i]); } printf("\n"); }
運行結果:spa
Before sort, This array has 5 items: 1 4 2 -9 0 After sort, This array has 5 items: -9 0 1 2 4 Before sort, This array has 6 items: 10 5 2 1 9 2 After sort, This array has 6 items: 1 2 2 5 9 10 Before sort, This array has 20 items: 13 -4 11 11 16 -12 -6 10 -8 2 0 5 -5 0 18 16 5 8 -14 4 After sort, This array has 20 items: -14 -12 -8 -6 -5 -4 0 0 2 4 5 5 8 10 11 11 13 16 16 18
從代碼可見,partition的過程是遍歷一次 個元素,而遞歸調用 partition 的次數與劃分是否平衡有關。最好的狀況是正好平衡,即每次劃分紅一半一半,這種狀況下partition 的次數相似於歸併排序中歸併的次數,即 ,因此快速排序的最佳時間複雜度爲 。.net
然而在最壞的狀況下,劃分極度不平衡,每次有一個組只有一個元素,此時快速排序將相似於插入排序,劃分次數會達到 ,因此快速排序的最壞時間複雜度爲 。
所幸最壞的狀況通常不會發生,通常來講快速排序的性能仍是至關不錯的。若是指望更加靠譜一點,能夠採用隨機化選取主元的方式。從代碼可見,咱們前面的主元選取都是直接選了第一個元素,若是第一個元素正好就是最小的元素,則可能發生最壞的狀況,而每次都隨機化選擇主元則能夠避免這個狀況。
由於快速排序能夠原址進行,因此這裏須要的空間 的。可是快速排序有遞歸調用,而調用的深度又取決於劃分的狀況,因此快速排序的最佳空間複雜度爲 ,最壞空間複雜度爲 。