前面讲了插入排序、选择排序、冒泡排序、归并排序以及冒泡排序的改进版鸡尾酒排序和插入排序的改进版希尔排序,下面来讲一种很经常使用的排序方法:快速排序。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
然而在最坏的状况下,划分极度不平衡,每次有一个组只有一个元素,此时快速排序将相似于插入排序,划分次数会达到 ,因此快速排序的最坏时间复杂度为 。
所幸最坏的状况通常不会发生,通常来讲快速排序的性能仍是至关不错的。若是指望更加靠谱一点,能够采用随机化选取主元的方式。从代码可见,咱们前面的主元选取都是直接选了第一个元素,若是第一个元素正好就是最小的元素,则可能发生最坏的状况,而每次都随机化选择主元则能够避免这个状况。
由于快速排序能够原址进行,因此这里须要的空间 的。可是快速排序有递归调用,而调用的深度又取决于划分的状况,因此快速排序的最佳空间复杂度为 ,最坏空间复杂度为 。