活动地址:CSDN21天学习挑战赛
📚“九层之台,起于垒土”。学习技术须脚踏实地。
☀️你要做冲出的黑马,而不是坠落的星星。
📖这里推荐一款刷题、模拟面试神器,可助你斩获心仪大厂offer:点我免费刷题、模拟面试
前言
本文的主题是快速排序,快排的应用很广。其流行的原因是它实现简单、适用于各种不同的输入数据且在一般应用中比其他排序算法都要快得多。
快速排序引人注目的特点包括它是原地排序,且将长度为 N 的数组排序所需的时间和 N lg N 成正比。
另外,快速排序的内循环比大多数排序算法都要短小,所以不论是在理论上还是在实际中都要更快。
快速排序的概念
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立地排序。快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序;而快速排序将数组排序的方式则是当两个子数组都有序时整个数组也就自然有序了。在第一种情况中,递归调用发生在处理整个数组之前;在第二种情况中,递归调用发生在处理整个数组之后。
快速排序的大致过程下所示。
所有图片来自算法(第四版)
算法描述
- 随机打乱数组。
- 选取数组的第一个(或随机一个)元素 pivot ,将小于 pivot 的元素置于 pivot 的左侧,大于 pivot 的元素置于 pivot 的右侧。如果数组长度为 1,则停止切分。
- 递归地将 pivot 两侧的子数组切分,即将两侧的数组分别应用第 2 步的切分。
- 当所有子数组长度为 1 时,排序完成。
C++实现
切分部分:
template<typename T>
int partition(T* a, int lo, int hi){
int i = lo, j = hi + 1;
T pivot = a[lo];
while(true){
while(a[++i] < pivot) if(i == hi) break;
while(pivot < a[--j]) if(j == lo) break;
if(i >= j) break;
swap(a[i], a[j]);
}
swap(a[lo], a[j]);
return j;
}
切分路径演示:
排序部分:
template<typename T>
void sort(T* a, int lo, int hi){
if(hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
排序演示:
复杂度分析
-
时间复杂度
将长度为 N 的无重复数组排序,快速排序平均需要 2 N l n N ~2NlnN 2NlnN次比较(以及 1/6 的交换)。
下面的证明仅做参考:
快速排序最多需要约 N 2 / 2 N^2/2 N2/2次比较,但随机打乱数组能够预防这种情况。
-
空间复杂度
快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中会消耗一定的空间,在一般情况下的空间复杂度为 O(logn),在最差的情况下,若每次只完成了一个元素,那么空间复杂度为 O(n)。所以我们一般认为快速排序的空间复杂度为 O(logn)。