快速排序:和归并排序递归算法类似,采用分而治之的思路,采用枢纽的方式将数组分成两大块(分),进而递归的解决枢纽左边的和解决枢纽右边的(治),将左边+枢纽+右边放进一个数组则完成快速排序。
//伪代码
void quickSort(ElementType A[],int N){
if (N < 2) return;
pivot = 从A[]中选一个主元;
将S = { A[] \ pivot } 分成2个独立子集:
A1 = { a∈S | a <= pivot } && A2={ a∈S | a >= pivot };
A[] = quickSort(A1,N1) ∪ {pivot} ∪ quickSort(A2,N2);
}
算法分析:由上面的伪代码可知,快速排序算法并不复杂,一个递归终止条件,一个pivot选取方法,一个划分过程,一个治理过程(递归调用),需要重点关注的是如何选择pivot和如何划分两个子集。划分子集的相对比较固定,而如何选择pivot则关系到算法的效率,最好情况是每次正好中分,从而整体时间复杂度是O(N log N)。
选主元:
- 最简单的方法是选A[0]? 不科学,容易导致两个子集分布很不均匀,甚至每次只能划分成一个子集导致算法退化到O(N^2)
- 随机取pivot? rand()函数开销大
- 三者取中法:取头中尾的中位数(推荐)
//三者取中函数
int median3(vector<int>& arr, int Left, int Right) {
int mid = Left + (Right - Left) / 2;
if (arr[Left] > arr[mid]) {
swap(arr[Left],arr[mid]);
}
if (arr[Left]>arr[Right]) {
swap(arr[Left],arr[Right]);
}
if (arr[mid]>arr[Right]) {
swap(arr[mid],arr[Right]); //arr[Left]<=arr[mid]<=arr[Right]
}
swap(arr[mid], arr[Right - 1]); //将pivot藏到右边
//只需要考虑arr[Left+1] ... arr[Right-1]
return arr[Right-1]; //返回pivot
}
子集划分:定义左指针i,定义右指针j,找到左边第一个大于pivot的位置,然后找到右边第一个小于pivot的位置,交换i,j位置的元素,直到左指针i大于右指针j,i最后的位置就是pivot的位置。以此划分pivot会放置在其最终位置。
问:如果有元素正好等于pivot怎么办? (举个例子:全是1)
- 停下来作交换?(多了无用的交换操作,i会停在中间位置,导致子集较为均分)
- 不理它,继续移动指针?(i一直移动,j没机会移动,子集不均分,时间复杂度退化到O(N^2))
- 综上,选择停下来作交换
问:小规模数据的处理,快速排序的问题
- 用递归会额外占用系统的堆栈空间,而且每一次调用系统堆栈都涉及push和pop操作,耗费时间
- 对于小规模的数据(比如小于100)可能还不如插入排序快
- 解决方案:对于大规模数据用快排递归,小规模数据用简单排序(比如插入排序),在程序中定义一个cutoff的阈值
#include<bits/stdc++.h>
using namespace std;
int median3(vector<int>& arr, int Left, int Right) {
int mid = Left + (Right - Left) / 2;
if (arr[Left] > arr[mid]) {
swap(arr[Left],arr[mid]);
}
if (arr[Left]>arr[Right]) {
swap(arr[Left],arr[Right]);
}
if (arr[mid]>arr[Right]) {
swap(arr[mid],arr[Right]); //arr[Left]<=arr[mid]<=arr[Right]
}
swap(arr[mid], arr[Right - 1]); //将pivot藏到右边
return arr[Right-1];
}
void qSort(vector<int>&arr,int Left,int Right) {
if (Right-Left+1 < 2) return; //保证至少有两个元素
int pivot = median3(arr,Left,Right);
int i = Left;
int j = Right - 1;
while (i<j) {
//改成i<j是因为当有两个元素的时候--j会越界,两个元素的排序通过median3就已经搞定了,i=left=right-1;
while (arr[++i] < pivot) {};
while (arr[--j] > pivot) {};
if (i<j)
swap(arr[i], arr[j]);
else
break;
}
swap(arr[i],arr[Right-1]);
qSort(arr, Left, i-1);
qSort(arr,i+1,Right);
}
void quickSort(vector<int>&arr) { //提供给外部统一接口,封装了一下qSort
int n = arr.size();
qSort(arr,0,n-1);
}
int main() {
vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
quickSort(arr);
for (auto x : arr) {
cout << x << " ";
}
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9 10
请按任意键继续. . .
性能分析:整体O(nlogn),考虑到堆栈保存耗时间,所以小规模数据(小于100)采用插入排序。
- 时间复杂度:O(n log n)
- 空间复杂度:O(log n)
- 稳定性:不稳定