参考书籍:《数据结构与算法分析——C语言描述》
快速排序是在实践中最快的已知排序算法,它的平均时间复杂度O(NlogN)。当然在最坏的情况下为O(N^2),但稍加努力就可以避免这种情形。
像归并排序一样,快速排序也是一种分治的递归算法,可简单表示如下:
将数组S排序的基本算法由下列简单的四步组成。
1、数组元素至少大于或等于4个,否则直接利用插入排序完成。
2、利用特定方法(三数中值分割法)取数组某一特定元素V,称之为枢纽元。
3、将数组中其他元素(即S-{V})分成两个不相交的子集。S1={x∈S-{V} | x≤V}和S2={x∈S-{V} | x≥V}。
4、继而对两个子集S1和S2进行同理1-3的递归运算。
如何选取枢纽元呢?
此处我们介绍是三数中值分割法。选取数组中最左端、最右端和中心位置的三个元素,比较三个元素的大小,并按照小的在左边交换彼此位置,取中间值作为枢纽元。
例如:
对于初始序列:
left=8;right=0;center=6。
显然此处枢纽元应为piovt=6;并且此时的排序为:
然后我们选取两个指示器i和j。从序列的首尾两端出发,除枢纽元外的元素与枢纽元进行比较。此时可把枢纽元6移至right-1的位置(因为right已经比piovt大了,left已经比piovt小了)。
即:
如果i指示的数比枢纽元小,则i++;直至遇到大于等于枢纽元的数停止。
如果j指示的数比枢纽元大,则j++;直至遇到小于等于枢纽元的数停止。
若此时i<j(说明元素没有全部比完),则交换i和j位置的数。继续上述的比较,直至i≥j(说明元素全部比完)。
比完时序列如下:
此时在交换位置i的数和枢纽元。如下图所示:
枢纽元已将原序列分割为两个子集:
1、大于枢纽元:7,9,8
2、小于枢纽元:0,1,4,2,3,5
同理可以将两个子集继续进行如此的分割排序。
代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 获取枢纽元 枢纽值被放在序列末尾 - 1的位置
void dealPivot(vector<int> &nums, int l, int r)
{
int mid = (l + r) / 2;
if (nums[l] > nums[mid])
{
swap(nums[l], nums[mid]);
}
if (nums[l] > nums[r])
{
swap(nums[l], nums[r]);
}
if (nums[mid] > nums[r])
{
swap(nums[mid], nums[r]);
}
swap(nums[mid], nums[r - 1]);
}
void quickSort(vector<int>& nums, int l, int r)
{
if (l < r)
{
dealPivot(nums, l, r);
int pivot = r - 1;
int i = l; // 左指针
int j = r - 1; // 右指针
while (1)
{
// 这里无需对i做安全校验,是因为pivot = r - 1; 所有++i不可能越界
while (nums[++i] < nums[pivot]);
// j > l是因为只有两个数时, pivot == l == j == 0
while (j > l && nums[--j] > nums[pivot]);
if (i < j)
{
swap(nums[i], nums[j]);
}
else
{
break;
}
}
if (i < r - 1)
{
swap(nums[i], nums[r - 1]);
}
quickSort(nums, l, i - 1);
quickSort(nums, i + 1, r);
}
}
int main()
{
vector<int> nums{ 9, 6, 7, 3, 8, 4, 5, 1, 2};
quickSort(nums, 0, nums.size() - 1);
for_each(nums.begin(), nums.end(), [](const int index) {
cout << index << " ";
});
cout << endl;
return 0;
}