快速排序(Quick Sort)
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。快速排序又是一种分而治之思想在排序算法上的典型应用。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(nlogn) 的排序算法表现要更好。快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
C++的标准库sort函数就是基于快速排序实现的,当然实现要复杂的多,里面有各种优化,当数据量比较少的时候采用的是插入排序。虽然C中的qsort函数也是基于快排的,但是sort要比qsort快的多。
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
动图演示
代码实现
1.常规挖坑法(基于数据结构严蔚敏版)
// 快排挖坑法
int partition(vector<int> &v, int l, int r)
{
int pivot = v[l];
int low = l, high = r;
while (low < high) {
while (low < high && v[high] >= pivot) { --high; }
v[low] = v[high];
while (low < high && v[low] <= pivot) { ++low; }
v[high] = v[low];
}
v[low] = pivot;
return low;
}
void quickSort(vector<int> &v, int l, int r)
{
if (l < r) {
int pivot = partition(v, l, r);
quickSort(v, l, pivot - 1);
quickSort(v, pivot + 1, r);
}
}
2.交换法(网上大神模板)
void quickSort(vector<int> &v, int l, int r)
{
if (l >= r) return;
int pivot = v[(l + r) >> 1];
int low = l - 1, high = r + 1;
while (low < high) {
do --high; while (v[high] > pivot);
do ++low; while (v[low] < pivot);
if (low < high) swap(v[low], v[high]);
}
quickSort(v, l, high);
quickSort(v, high + 1, r);
}
算法分析
快速排序每次选取基准元素划分的时候期望把序列分成大小相同的两个序列,即期望为O(nlogn)的,但如果输入序列为顺序序列,则每次划分的元素都将处于基准元素(此时基准元素每次取首元素或尾元素)的一边,这样就退化为O(n2)的。所以,快速排序的平均时间复杂度为O(nlogn),最坏时间复杂度为O(n2)。它的时间复杂度跟输入序列和选取的基准元素有关。空间复杂度主要是进行递归时消耗的栈空间,所以空间复杂度为O(logn)。快速排序是不稳定排序。