描述
快速排序是一种常用的排序算法,它的思想是选取一个基准元素,将数组分成左右两部分,使得左边的元素都小于等于基准元素,右边的元素都大于等于基准元素,然后递归地对左右两部分进行快速排序。
实现步骤
- 选择一个基准元素,通常可以选择数组的第一个元素。
- 定义左指针和右指针,分别指向数组的第一个元素和最后一个元素。
- 左指针往右移动,直到找到一个大于基准元素的元素;右指针往左移动,直到找到一个小于基准元素的元素;交换左右指针指向的元素。
- 重复步骤3,直到左指针和右指针相遇。
- 将基准元素和左指针指向的元素进行交换,此时基准元素的位置就是它最终的位置。
- 递归地对左右两部分进行快速排序。
图解
时间复杂度
快速排序的时间复杂度为O(nlogn)。
在快速排序中,每次划分都将数组分成左右两部分,直到每个部分中只有一个元素或者没有元素为止。每次划分的时间复杂度为O(n),其中n为待排序数组的长度。划分过程中需要比较元素的大小和交换元素的位置,所以时间复杂度为O(n)。
假设待排序数组的长度为n,每次划分后,左右两部分的长度分别为n/2。递归地对左右两部分进行快速排序,时间复杂度可以表示为:
T(n) = 2 * T(n/2) + O(n)
根据主定理(Master Theorem)的第三种情况,即递归式形如T(n) = a * T(n/b) + f(n),其中a>=1,b>1,f(n)为多项式,如果存在常数c<1和常数d>=0,使得f(n) = O(n^d * log^c(n)),那么时间复杂度可以表示为:
T(n) = O(n^d * log^(c+1)(n))
在快速排序中,a=2,b=2,f(n) = O(n),所以d=1,c=0。根据上述公式,可以得到时间复杂度为O(nlogn)。
需要注意的是,这个时间复杂度是平均情况下的时间复杂度。在最坏情况下,即每次划分只能将一个元素移到它的最终位置上,快速排序的时间复杂度为O(n^2)。为了避免最坏情况的发生,可以采用随机选择基准元素或者选择三个元素中的中值作为基准元素。
空间复杂度
快速排序的空间复杂度为O(logn)。
快速排序是一种原地排序算法,即在排序过程中不需要额外的辅助空间,只需要利用原始数组进行元素的交换和移动。因此,快速排序的空间复杂度主要由递归调用所占用的栈空间决定。
在快速排序的实现过程中,每次划分都会将数组分成左右两部分,然后分别对这两部分进行快速排序。递归最多进行logn次,因为每次划分都会将数组的规模减半。在递归的过程中,每层递归都需要保存一些临时变量,包括基准元素的索引、左右指针的位置等,这些变量占用的空间与递归的层数成正比。
因此,快速排序的空间复杂度为O(logn)。
示例
#include <iostream>
using namespace std;
void quickSort(int arr[], int left, int right) {
if (left >= right) {
return;
}
int pivot = arr[left];
int i = left;
int j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) {
j--;
}
while (i < j && arr[i] <= pivot) {
i++;
}
if (i < j) {
swap(arr[i], arr[j]);
}
}
swap(arr[left], arr[i]);
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
int main() {
int arr[] = {5, 2, 8, 9, 1};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
cout << "quickSort res :" << endl;
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
结果:
注意事项
快速排序的一个重要技巧是如何选择基准元素。如果选择的基准元素恰好是数组的最大或最小值,那么每次划分都只能将一个元素移到它的最终位置上,这样快速排序的时间复杂度将会退化为O(n^2)。为了避免这种情况,可以随机选择基准元素,或者选择三个元素中的中值作为基准元素。
另外一个需要注意的地方是对于小规模的数组,快速排序的性能可能会比插入排序差,因此可以在递归到一定程度时,切换到插入排序来提高性能。