快速排序
简介
快速排序是对冒泡排序的一种改进, 它是不稳定的。由C. A. R. Hoare在1962年提出的一种划分交换排序,采用的是分治策略(一般与递归结合使用),以减少排序过程中的比较次数,它的最好情况为O(nlogn),最坏情况为O(n^2),平均时间复杂度为O(nlogn)。
基本思想
选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到全部数据变成有序。
步骤
(1) 从数列中挑出一个基准值。
(2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边),在这个分区退出之后,该基准就处于数列的中间位置。
(3) 递归地把基准值前面的子数列和基准值后面的子数列进行排序。
注意:基准元素/左游标/右游标都是针对单趟排序而言的, 也就是说在整个排序过程的多趟排序中,各趟排序取得的基准元素/左游标/右游标一般都是不同的。对于基准元素的选取,原则上是任意的,但是一般我们选取数组中第一个元素为基准元素(假设数组随机分布)。
算法步骤(图示样例)
-
举个测试例子
-
-
该例子完整的算法过程
-
递归方法
//快排的递归方法
int parition(int* br, int left, int right) {
int tmp = br[left];
while (left < right) {
while (tmp <= br[right] &&left<right) --right;
if (left < right) br[left] = br[right];
while (tmp > br[left] && left <right) ++left;
if (left < right) br[right] = br[left];
}
br[left] = tmp;
return left;
}
void recursion(int* ar, int left, int right) {
if (left < right) {
int pos = parition(ar, left, right);
recursion(ar, left, pos - 1);
recursion(ar, pos + 1, right);
}
}
void quick_sort1(int* br, int left, int right) {
if (br == nullptr || left >= right) return;
recursion(br, left, right);
}
- 非递归方法(用队列去存储原来递归过程中产生的范围值)
/*
快排的非递归
非递归就是用循环去模拟递归过程,这个时候就必须十分熟悉递归调用的具体过程
*/
//方法1
void un_recursion2(int* ar, int left, int right) {
queue<pair<int,int>> qu;
qu.push(pair<int, int>(left, right));//首先保证队列中至少有一组数据
while (!qu.empty()) {
int i = qu.front().first;
int j = qu.front().second;
qu.pop();
int pos = parition(ar, i, j);
if (i < pos-1) {
qu.push(pair<int,int>(i, pos - 1));
}
if (pos + 1 < j) {//紧接着添加就是为了使队列中在左边划分完成的时候,队列中还能够保存有初始状态的右边的起始数值
qu.push(pair<int, int>(pos + 1, j));
}
}
}
//方法2
void un_recursion1(int* ar, int left, int right) {
queue<int> q;
q.push(left);
q.push(right);
while (!q.empty()) {
int i = q.front();
q.pop();
int j = q.front();
q.pop();
int pos = parition(ar, i, j);
if (i < pos) {
q.push(i);
q.push(pos - 1);
}
if(pos+1<j){
q.push(pos+1);
q.push(j);
}
}
}
void quick_sort2(int* br, int left, int right) {
if (br == nullptr || left >= right) return;
//un_recursion1(br, left, right);
un_recursion2(br, left, right);
}
- 随机划分(随机找到一个数值为参照物)
//快排的划分函数的参照物数值的随机取值
int parition(int* br, int left, int right) {
int tmp = br[left];
while (left < right) {
while (tmp <= br[right] &&left<right) --right;
if (left < right) br[left] = br[right];
while (tmp > br[left] && left <right) ++left;
if (left < right) br[right] = br[left];
}
br[left] = tmp;
return left;
}
int ran_partition(int* br, int left, int right) {
srand(time(nullptr));
int rnd = rand() % (right - left + 1) + left;
/*+1是为了得到范围内的最大长度,+left是为了得到
一定是交换范围里面的随机值,即绝对位置而不是相对位置*/
swap(br[left], br[rnd]);
return partition(br, left, right);
}
void quick_sort3(int* br, int left, int right) {
if (left < right) {
int pos = ran_partition(br, left, right);
quick_sort3(br, left, pos - 1);
quick_sort3(br, pos + 1, right);
}
}
- 三位取中划分(找到范围内的中位数与左边界交换后然后再以左边界的值为参照物然后划分)
//快排的划分函数的参照物数值的三位取中方法取值
int parition(int* br, int left, int right) {
int tmp = br[left];
while (left < right) {
while (tmp <= br[right] &&left<right) --right;
if (left < right) br[left] = br[right];
while (tmp > br[left] && left <right) ++left;
if (left < right) br[right] = br[left];
}
br[left] = tmp;
return left;
}
int mid_partition(int* br, int left, int right) {
int mid = (right + left) / 2;
struct node {
int index;
int value;
operator int()const {//重载一个强转运算符,
//为了使得使用优先队列排序的时候能够比较的是数值而不是下标
return value;
}
};
struct node ln = { left,br[left] };
struct node mn = { mid,br[mid] };
struct node rn = { right,br[right] };
priority_queue<node> pque;
pque.push(ln);
pque.push(mn);
pque.push(rn);
pque.pop();//弹出来一个,那么剩下的队头的就是中位数的结点
swap(br[left], br[pque.top().index]);
return partition(br, left, right);
}
void quick_sort4(int* br, int left, int right) {
if (left < right) {
int pos = mid_partition(br, left, right);
quick_sort4(br, left, pos - 1);
quick_sort4(br, pos + 1, right);
}
}