快速排序的算法是一种分而治之的算法,是一个递归算法。在通常情况下,快速排序算法是已知的最快的算法,故称这个算法为快速排序。通常情况指数据的分布比较平均,关键的的范围比较大。当关键字为升序或者逆序,归并排序比快速排序快的多。当关键字的范围非常窄,计数排序的时间复杂度可低至O(n).
其方法是:
1.首先找出一个枢轴元素base。
2.扫描待排序的数组,将这个数组划分为2部分,使左部分的所有元素小于等于base,右部分的所有元素大于base。
3.对左部分的子数组继续做这个操作。
4.对右部分的子数组做同样的操作。
5.继续递归下去,直到数组的长度小于为1.
下面的代码是这个算法的一个实现。其核心为partition_sort函数,这个函数的对数组的一个子区间 [low,high]做一个扫描。
这个算法取arr[low]作为枢轴元素,采用双向扫描方法。
如果元素大于等于枢轴元素则向左扫描,right--, 如果元素小于枢轴元素,则向右扫描,left++。
扫描结束后将原始区间[low,right] 分为3个子区间,[low,left-1], 中间区间[left,left],只包括一个元素,右区间[left+1,high],然后递归调用partition_sort对左右区间做同样的处理。
下面的代码中,当区间长度小于等于INSERT_SORT_THRESHOLD,调用插入排序算法。
#include <stdio.h>
#include <stdlib.h>
#include "sorts.h"
void partition_sort(ELE_TYPE arr[], int low, int high)
{
ELE_TYPE base;
int left,right;
if ( high-low+1 <= INSERT_SORT_THRESHOLD)
{
insert_sort(arr+low, high-low+1);
return ;
}
left=low;right=high;
base= arr[low];
while (right>left)
{
while (right>left && arr[right]>= base)
right--;
if ( right>left)
{ arr[left]=arr[right]; left++;} // fill the hole, the hole is arr[left]
while (left<right && arr[left]< base)
left++;
if ( left<right)
{
arr[right]=arr[left]; right--; // fill the hole, the hole is arr[right]
}
}
arr[left]=base; // now, the left==right
partition_sort(arr,low, left-1);
partition_sort(arr,left+1, high);
}
void quick_sort(ELE_TYPE arr[], int len)
{
partition_sort(arr,0,len-1);
}
void test_quick_sort()
{
ELE_TYPE arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
int len = (int) sizeof(arr) / sizeof(arr[0]);
printf("original data are:");
print_array(arr, len);
quick_sort(arr, len);
printf("The data after sorted are:");
print_array(arr, len);
}
上面的实现是一个最简单的版本。但是这个版本有个缺点。对于特定的数据分布,如数组是严格的非递减序列或者非递增序列,这个算法的递归深度会增加到n,n为数组长度。时间复杂度增加到O(n^2),空间复杂度增加到O(n)。性能变差还是小事儿,要命的是,由于栈的空间比较小,当数组较大时,会导致栈溢出,程序崩溃。
为了解决问题,人们想出各种方法。一种方法是,在序列中随机选择一个元素作为枢轴元素。另一种方法是,选择数组最左,中间,最右的三个元素的中间值作为枢轴,简称三者取中法。
对数组的扫描方法也不限于双向扫描。算法导论(第二版)第7章 给出的方法是
采用自左向右的方向扫描数组, 将待排序的数组分为3部分,左边的部分<=x, x为枢轴元素,中间的部分>x,右边的部分为未排序部分。