快速排序(Quick Sort):
快速排序的原理:参考这篇 快速排序(过程图解)
快速排序的代码实现:
#include <iostream>
using namespace std;
void swap(int k[], int low, int high)
{
int temp;
temp = k[low];
k[low] = k[high];
k[high] = temp;
}
int Partition(int k[], int low, int high)
{
int point;
point = k[low];
while (low < high)
{
while (low < high && k[high] >= point) //一定要注意这里是大于等于,如果
//换成大于号,当有相同的元素进
//行比较时会出现死循环
{
high--;
}
swap(k, low, high);
while (low < high && k[low] <= point) //同时注意这里是小于等于
{
low++;
}
swap(k, low, high);
}
return low;
}
void QSort(int k[], int low, int high)
{
int point;
if (low < high)
{
point = Partition(k, low, high);
QSort(k, low, point - 1);
QSort(k, point + 1, high);
}
}
void QuickSort(int k[], int n)
{
QSort(k, 0, n - 1);
}
int main()
{
int i, a[10] = { 4, 2, 5, 0, 3, 9, 1, 7, 6, 8 };
cout << "排序前的数组是:";
for (i = 0; i < 10; i++)
{
cout << a[i];
}
cout << endl;
QuickSort(a, 10);
cout << "排序后的数组是:";
for (i = 0; i < 10; i++)
{
cout << a[i];
}
cout << endl;
return 0;
}
实现结果:
在快速排序中,我们会选取一个关键字,然后想尽办法将它放到一个位置,是的它左边的值都比它小,右边的值都比它大,我们把这样的关键字叫做枢轴(privot)。
显然枢轴的选取对快排的效率是有影响的。除了枢轴的选取,还有其他的一些因素也能对快排的效率造成影响,
快速排序的优化:
1)优化选取枢轴
之前代码中的枢轴的选取都是直接取第一个数字,如果这个数字大小比较靠中间还好,如果比较偏大或者偏小对排序都不好。
三数取中法:即取三个关键字进行排序,将中间的作为枢轴,一般取左端、右端和中间三个数。
还有三数取中法的改进版本,九数取中法,是将三数取中法做3次,再取出最中间的数。
2)优化不必要的交换
3)优化小数组时的排序方案
在数组较小的时候,我们直接用简单排序中表现较好的直接插入排序。
4)优化递归操作
使用尾递归:
对于尾递归的解释,可以参考百度百科这篇讲解
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
快速排序的时间复杂度和空间复杂度分析:
时间复杂度:
最优:O(nlogn)
最差:O(n²)
平均:O(nlogn)
空间复杂度:
最优:O(logn)
最差:O(n)
平均:O(logn)
快速排序的稳定性分析:
由于快速排序中,关键字的比较和交换都是跳跃进行的,因此,快速排序是一种不稳定的排序算法。