快排:
一趟排序将要序列分割成独立的两部分,一部分的所有数据都比另外一部分的所有数据都要小,按此方法再对这两部分数据分别进行快速排序,整个排序过程可以递归进行。
void quickSort(vector<int> &a, int beg, int end){
if (beg >= end) return;
auto q = partition(a, beg, end);
quickSort(a, beg, q-1);
quickSort(a, q+1, end);
}
数据分割的过程需要一个分界值,作为两部分的"分界",一般取序列第一个或者最后一个。有了分界值后,如何将序列划分为两部分就是快排的核心问题,也就是如何设计上述的partition
函数。
双指针法
假设序列为a, a.size() == n
, 初始输入上下界即为low = 0
,high = n-1
, 双指针分别用i
, j
表示:
-
选取分界值
pivot = a[high]
-
i = low, j = high
(分别是待处理序列(不包括分界值a[n]
)的首尾元素下标) -
i
往右走,直到:a[i] > key
,这儿一定是i
先走,下一步j
再走。
j
往左走,直到:a[j] < key
然后交换a[i]
和a[j]
-
循环上一步直到
i
和j
相遇,最后将a[high]
与a[i]
交换。
直接贴代码吧:
// 双指针(pivot为末尾)
int partition1_1(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low, j = high;
while (i < j){
// 先看左边(顺序不能错)
while (i < j && a[i] <= pivot) ++i;
// 再看右边
while (i < j && a[j] >= pivot) --j;
swap(a[i], a[j]);
}
swap(a[high], a[i]);
return i;
}
如果选取的分界值pivot = a[low]
,那么代码少许不同,一开始我也没有注意到:
// 双指针(pivot为开头)
int partition1_2(vector<int> &a, int low, int high){
auto pivot = a[low];
int i = low, j = high;
while (i < j){
// 这儿是j先走,先看右边
while (i < j && a[j] >= pivot) --j;
// 再看左边
while (i < j && a[i] <= pivot) ++i;
swap(a[i], a[j]);
}
swap(a[low], a[i]);
return i;
}
不知道叫啥名?
用已处理区间与未处理区间的方式来理解:
假设序列为a, a.size() == n
, 初始输入上下界即为low = 0
,high = n-1
, 双指针分别用i
, j
表示::
-
选取分界值
pivot = a[high]
-
i = low, j = low
(开始时已处理区间为空,i
一直指向未处理区间的第一个元素,也就是已处理区间的下一个元素,j
则用来进行遍历 -
最后
pivot
与已处理区间的后面那个元素(i
)交换即可。
贴个图理解:
// 单边法(末尾为pivot)
int partition2_1(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
while (j < high){
if (a[j] < pivot)
swap(a[i++], a[j]); //已处理区间多了一个元素,右边界增加1
++j;
}
swap(a[high], a[i]);
return i;
}
//换种写法(pivot同样为末尾)
int partition2(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low;
for (int j = low; j < high; ++j){
if (a[j] < pivot)
swap(a[j], a[i++]);
}
swap(a[i], a[high]);
return i;
}
如果以开头作为pivot
:
// 单边法(开头为pivot)
int partition2_2(vector<int> &a, int low, int high){
auto pivot = a[low];
int i = low+1, j = low+1;//i指向以处理区间右边界,j用来遍历元素
while (j <= high){
if (a[j] < pivot)
swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
++j;
}
swap(a[low], a[i-1]);
return i-1;
}
三数取中法
数据结构与算法之美:14 | 排序优化:如何实现一个通用的、高性能的排序函数
关于三数取中法的理解,可以看这个:快速排序之三数取中法,思想还是很简单的,实现的时候需要注意细节。
如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为 O(n^2)。这种 O(n^2) 时间复杂度出现的主要原因还是因为分区点选的不够合理。
最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多
比较常用、比较简单的分区算法就是三数取中法和随机法。
void getMid(vector<int> &a, int low, int high){
auto mid = (high + low) / 2;
if (a[low] > a[mid]) swap(a[low], a[mid]);
if (a[low] > a[high]) swap(a[low], a[high]);
if (a[mid] > a[high]) swap(a[mid], a[high]);
swap(a[mid], a[high-1]);
}
int partition3(vector<int> &a, int low, int high){
getMid(a, low, high);
auto pivot = a[high-1];
//i, j一定从low开始,如果从low+1开始只有两个元素时会出错
int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
while (j < high-1){
if (a[j] < pivot)
swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
++j;
}
swap(a[high-1], a[i]);
return i;
}
随机法
我们每次选取pivot之前随机选择一个元素与末尾元素交换,然后再以末尾元素作为pivot:
int randomPartition(vector<int>& a, int low, int high) {
int i = rand() % (high - low + 1) + low; //随机一个i,i在区间[low, high]
swap(a[i], a[high]);
return partition(a, low, high);
}
int partition(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low, j = low;
while(j < high){
if(a[j] > pivot){ //这儿选择将比pivot大的放在左边
swap(a[j], a[i]);
++i;
}
++j;
}
swap(a[i], a[high]);
return i;
}
测试代码:
#include <iostream>
#include <vector>
using namespace std;
// 双指针(pivot为末尾)
int partition1_1(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low, j = high;
while (i < j){
// 先看左边(顺序不能错)
while (i < j && a[i] <= pivot) ++i;
// 再看右边
while (i < j && a[j] >= pivot) --j;
swap(a[i], a[j]);
}
swap(a[high], a[i]);
return i;
}
// 双指针(pivot为开头)
int partition1_2(vector<int> &a, int low, int high){
auto pivot = a[low];
int i = low, j = high;
while (i < j){
while (i < j && a[j] >= pivot) --j;
while (i < j && a[i] <= pivot) ++i;
swap(a[i], a[j]);
}
swap(a[low], a[i]);
return i;
}
// 单边法(末尾为pivot)
int partition2_1(vector<int> &a, int low, int high){
auto pivot = a[high];
int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
while (j < high){
if (a[j] < pivot)
swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
++j;
}
swap(a[high], a[i]);
return i;
}
// 单边法(开头为pivot)
int partition2_2(vector<int> &a, int low, int high){
auto pivot = a[low];
int i = low+1, j = low+1;//i指向以处理区间右边界,j用来遍历元素
while (j <= high){
if (a[j] < pivot)
swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
++j;
}
swap(a[low], a[i-1]);
return i-1;
}
void quicksort(vector<int> &a, int low, int high){
if (low >= high) return;
auto q = partition2_2(a, low, high);
quicksort(a, low, q-1);
quicksort(a, q+1, high);
}
int main()
{
vector<int> a{2,5,1,4,3,0,9,7};
int n = a.size();
quicksort(a, 0, n-1);
for (const auto &c : a) cout << c << " ";
cout << endl;
return 0;
}