快速排序
算法思路
- 每次都确定一个元素的最后位置,同时这个位置左边的数都是比它小,右边的数都是比它大
- 挖坑思路:
- 两个指针,一个left,一个right
- 每次大循环选一个枢轴pivotkey(选最左边的,其实选什么都没关系),相当于挖了一个坑
- 我们要从右边找到一个比v[pivotkey]小的元素,填上这个坑
- 填上最初的坑,right的位置又多了一个新的坑,我们要从左边找出一个比v[pivotkey]大的元素,填上新的坑
- 循环直到left==right,这个位置就是flag最后的位置
- 再进行递归,分别flag左边的数组和右边的数组进行排序
- 非递归和递归实现类似,非递归就是用显式stack代替递归中使用的系统栈,存储每次排序后枢轴左右两边的两个数组的起始位置和结束位置
书面概括
待排序的n个元素中任取一个元素(通常取第一个元素)作为枢轴(或支点),设其关键字为pivotkey。经过一趟排序后,把所有关键字小于pivotkey的元素交换到前面,把所有关键字大于pivotkey的元素交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别对左、右子表重复上述过程,直至每一子表只有一个元素时,排序完成。
其中,一趟快速排序的具体步骤如下。
- 选择待排序表中的第一个元素作为枢轴,把枢轴暂存在r[0]位置上。附设两个指针low和high,初始时分别指向表的下界和上界,(第一趟,low = 1;high = L.length)。
- 从表的最右侧位置依次向左搜索,找到第一个关键字小于枢轴关键字pivotkey的元素,将其移到low处。具体操作时:当low<high时,若high所指元素的关键字大于等于pivotkey,则向左移动指针high(执行操作:high--);否则将high所指元素与枢轴元素交换。
- 然后再从表的最左侧位置,依次向右搜索找到第一个关键字大于pivotkey的元素和枢轴元素交换。具体操作是:当low<high时,若low所指元素的关键字小于等于pivotkey,则向右移动指针low(执行操作:low++);否则将low所指元素与枢轴元素交换。
- 重复步骤2和3,直到low和high相等为止,此时low或high的位置即为枢轴在此趟排序中的最终位置,原表被分为两个子表。
在上述过程中,元素的交换都是与枢轴之间发生的,每次交换都要移动3次元素,可以先将枢轴元素暂存在r[0]位置上,排序过程中只需移动要与枢轴交换的元素,即只做r[low]和r[high]的单向移动,直至一趟排序结束后再将枢轴元素移至正确的位置。
考虑情况
排序过程会有三种情况
- 待排序元素大于有序序列中最大的元素;
- 待排序元素小于有序序列中的元素,但不小于有序序列中最小的元素;
- 待排序元素小于有序序列中最小的元素,但是因为监视哨的存在,2和3的情况归为一类。
算法效率
- 最好情况:待排序序列为顺序序列
- 比较次数KCN:n-1;
- 移动次数RMN:0;
- 最坏情况:待排序序列为逆序序列
- 比较次数KCN:2 + 3 + ... + n ≈ n²/2;
- 移动次数RMN:(2 + 1) + (3 + 1) + ... + (n + 1) ≈ n²/2;
所以时间复杂度为O(nlogn);
总结一句,就是递归树深度是logn,每次递归都要进行一次O(n)的交换,所以是O(nlogn)
空间复杂度为O(1),监视哨。
算法特点
- 不稳定排序;
- 链式存储结构也适合;
- 更适合于初始记录基本有序(正序)的情况,当序列完全无序,尤其是逆序,且元素过多,时间复杂度会大大提高。
算法代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int partition(vector<int>& v, int l, int r) {
int flag = v[l];
while (l < r) {
while (l < r && v[r] >= flag) r--;
v[l] = v[r];
while (l < r && v[l] <= flag) l++;
v[r] = v[l];
}
v[l] = flag;
return l;
}
//递归
void quickSortRecursion(vector<int>& v, int l, int r) {
if (l >= r) return;
int pivotkey = partition(v, l, r);
quickSortRecursion(v, l, pivotkey-1);
quickSortRecursion(v, pivotkey+1, r);
}
//非递归
void quickSortNonRecursion(vector<int>& v, int l, int r) {
stack<pair<int, int>> s;
s.push(pair<int,int>(l, r));
while (!s.empty()) {
pair<int, int> p= s.top();
s.pop();
int left =p.first, right = p.second;
int leftRec = p.first, rightRec = p.second;
if (left >= right) continue;
int flag = v[left];
while (left < right) {
while (left < r && v[right] >= flag) right--;
v[left] = v[right];
while (left < right && v[left] <= flag) left++;
v[right] = v[left];
}
v[left] = flag;
s.push(pair<int, int>(leftRec, left-1));
s.push(pair<int, int>(left+1, rightRec));
}
}
int main() {
vector<int> v = { 49,38,65,97,76,13,27,49 };
vector<int> s = { 49,38,65,97,76,13,27,49 };
quickSortRecursion(v, 0, v.size() - 1);
quickSortNonRecursion(s, 0, s.size()-1);
for (auto a : v) cout << a << " ";
cout << endl;
for (auto a : s) cout << a << " ";
cout << endl;
return 0;
}