快速排序被列为20世纪十大算法之一。
基本思想是:选取一个枢轴,通过一趟排序将待排记录分割成独立的俩部分,其中一部分记录比枢轴小,另外一部分记录比枢轴大;然后再继续对分割好的的字序列进行上述排序。
实现代码
void swap(int *a, int low, int high)
{
int temp = a[low];
a[low] = a[high];
a[high] = temp;
}
// 交换a中字表的记录,是枢轴记录到位,并返回其位置
// 此时枢轴之前(后)的记录均不大(小)于它
int Partition(int *a, int low, int high)
{
int pivotKey = a[low]; // 用字表的第一个记录作枢轴记录
while (low < high) // 从表的俩端交替向中间扫描
{
while (low < high && pivotKey <= a[high])
{
high--;
}
swap(a, low, high); // 将比枢轴记录小的记录交换到低端
while (low < high && pivotKey >= a[low])
{
low++;
}
swap(a, low, high); // 将比枢轴记录大的记录交换到高端
}
return low; // 返回枢轴的位置
}
// 对顺序表a的子序列作快速排序
void QSort(int *a, int low, int high)
{
int pivot;
if (low < high)
{
pivot = Partition(a, low, high); // 将a一分为二、返回枢轴位置
QSort(a, low, pivot - 1); // 对低字表递归排序
QSort(a, pivot + 1, high); // 对高子表递归排序
}
}
// 对序列表a进行快速排序
void QuickSort(int *a, int len)
{
QSort(a, 0, len - 1);
}
快速排序优化
- 优化选取枢轴
直接选取表的第一个记录作为枢轴,如果第一个记录正好是整个表的中间数是最理想的,但实际情况是第一个记录往往偏大或偏小,甚至极端情况是最大或最小,此时第一个记录作为枢轴显然是不合适的,故一般采用“三数取中”法——即选取头尾和中间三个数,也可速记选取,取中间值作为枢轴。或“九数取中”法,即选取三组数,算出分别算出中间值,再在这三个中间值中选取中间值作为枢轴。
int mid = low + (high - low) / 2;
if (a[low] > a[high])
{
swap(a, low, high);
}
if (a[mid] > a[high])
{
swap(a, mid, high);
}
if (a[low] < a[mid])
{
swap(a, low, mid);
}
int pivotKey = a[low]; // 用字表的第一个记录作枢轴记录
- 优化小数组时的排序方案
当数组较小时,直接采用直接插入排序,性能更好
对QSort函数进行改进即可。
high - low 大于某个常数,有资料说7合适,也有说50合适,实际情况可自行调整。
#define MAX_LEN_INSERT_SORT 7
void QSort(int *a, int low, int high)
{
int pivot;
if ((high - low) > MAX_LEN_INSERT_SORT)
{
pivot = Partition(a, low, high); // 将a一分为二、返回枢轴位置
QSort(a, low, pivot - 1); // 对低字表递归排序
QSort(a, pivot + 1, high); // 对高子表递归排序
}
else
{
InsertSort(int *a, int len);
}
}
- 优化递归操作
void QSort(int *a, int low, int high)
{
int pivot;
if ((high - low) > MAX_LEN_INSERT_SORT)
{
while (low < high)
{
pivot = Partition(a, low, high); // 将a一分为二、返回枢轴位置
QSort(a, low, pivot - 1); // 对低字表递归排序
low = pivot + 1; // 尾递归优化法
}
}
else
{
InsertSort(int *a, int len);
}
}