一、原理
快速排序(QuickSort)主要是采用分治处理的方法,将一个无序(或有序)的数据一分为二,使其左边的数都比基准数(Pivot)小,而右边都比它大。此后分别递归处理左右两端,就可以完成排序。
二、流程
1.划分区间范围,用L和R变量作为索引(需要参数传进来的左右区间low和high赋值给L和R),分别指向最左端和最右端的索引值。注意调整递归出口条件:low大于等于high
2.找到一个关键字(Key),如规定L索引的值是key。
3.先动右边的指针,如果R所指的数据比key大,那么while循环继续将R往左边靠拢,直至重合或数据比key小。若找到一个比key小的数据那么就将L和R的指针所指向的数据交换。
4.交换完毕后,调换指针运动方向,改变为L向右运动,如果L所指的数据比key小,那么while循环继续将L往左边靠拢,直至重合或数据比key大。
5.通过以上3、4步的不断的改变,直至两个指针重合时停止,完成一次排序。
6.递归处理两端数据,分别是最左端区间low和R-1,以及最右端区间high和L+1,两次递归即可。
三、代码
void QuickSort(int arr[], int low, int high) {
if (low >= high) return;//递归出口条件:当高低指针大小相反或指针重合
int i = low, j = high;//置指针
int key = arr[i];//设定关键字
while (i < j) {//当指针大小顺序正确的时候
while (i < j && arr[j] >= key) j--;//只动高指针
swap(arr[i], arr[j]);//当遇到需要交换的数据或者指针重合时
//调换顺序
while (i < j && arr[i] <= key) i++;//只动低指针
swap(arr[i], arr[j]);//当遇到需要交换的数据或者指针重合时
}
QuickSort(arr, low, j - 1);
QuickSort(arr, i + 1, high);
}
四、时间复杂度分析
1、最好情况:当数组正好分为两个长度长等的子数组
T
(
n
)
=
{
O
(
1
)
n = 1
2
T
(
n
2
)
+
O
(
n
)
n > 1
T(n)= \begin{cases} O(1) & \text {n = 1} \\ {2T(\frac{n}{2}) + O(n)} & \text{n > 1} \end{cases}
T(n)={O(1)2T(2n)+O(n)n = 1n > 1
由此我们可以得出最终表达式:
T
(
n
)
=
O
(
n
log
2
n
)
T(n) = O(n{\log _2}n)
T(n)=O(nlog2n)
2、最坏情况:当数组分为0和n-1个长度的子数组
T
(
n
)
=
{
O
(
1
)
n = 1
T
(
n
−
1
)
+
O
(
n
)
n > 1
T(n)= \begin{cases} O(1) & \text {n = 1} \\ {T(n - 1) + O(n)} & \text{n > 1} \end{cases}
T(n)={O(1)T(n−1)+O(n)n = 1n > 1
由此我们可以得出最终表达式:
T
(
n
)
=
O
(
n
2
)
T(n) = O(n^2)
T(n)=O(n2)
3、平均情况:除最坏情况,其他情况会产生深度为
O
(
log
2
n
)
O({\log _2}n)
O(log2n)的递归树,每层均是
O
(
n
)
O(n)
O(n)
T
(
n
)
=
{
O
(
1
)
n = 1
T
(
n
−
1
)
+
O
(
n
)
n > 1
T(n)= \begin{cases} O(1) & \text {n = 1} \\ {T(n - 1) + O(n)} & \text{n > 1} \end{cases}
T(n)={O(1)T(n−1)+O(n)n = 1n > 1
由此我们可以得出最终表达式:
T
(
n
)
=
O
(
n
log
2
n
)
T(n) = O(n{\log _2}n)
T(n)=O(nlog2n)
五、优化快速排序
1、减少交换次数:不难发现,在基础版本上,交换可能发生在L=R上,那么这大大地增加了交换次数,导致处理大型数据时可能会造成时间过长(TLE)。只需要将while部分代码变为如下,即可减少交换次数
原理:当L=R时,只需要交换基准元素,否则交换L和R的元素
while (i < j) {//当指针大小顺序正确的时候
while (i < j && arr[j] >= key) j--;//只动高指针
while (i < j && arr[i] <= key) i++;//只动低指针
swap(arr[i], (i == j) ? arr[low] : arr[j]);//减少一半交换次数
}
2、随机化:我们容易发现,在固定形式下,平均情况和最好情况的时间复杂度几乎相等,那么我们可以进行随机取值以达到我们的期望
int i = low, j =high, pivot = rand() % (high - low + 1) + low;//随机化
swap(arr[i], arr[pivot]);//基准数交换
int key = arr[i];//设定关键字
3、小区间插入排序:在经典形式下,当分割的数组长度小于一定范围时,使用插入排序速度比快速排序更快,所以我们改用数组区间小于10时使用插入排序
void InsertSort(int arr[], int i, int j) {//变种版
for (int l = i + 1; l <= j; l++) {
int k = l, key = arr[l];
while (arr[k - 1] > key) {
arr[k] = arr[k - 1];
k--;
if (k == i) break;
}
arr[k] = key;
}
}
void QuickSort_opt3(int arr[], int low, int high) {
//当区间长度小于10的时候改为插入排序,加快速度
if (low >= high) return;//递归出口条件:当高低指针大小相反或指针重合
if (high - low + 1 < 10) {
InsertSort(arr, low, high);
return;
}
//………………
}
代码分析来源于B站UP:鹤翔万里