一、为什么要用三路划分快排?
前面我们已经实现了三个版本的快速排序的算法,分别是hoare法,挖坑法和前后指针法。但是前面的三个版本的快速排序在某些极端场景中效率都会变得很低,例如大部分都是同一个数的时候,前面的三种方法都不能很高效地完成排序,时间复杂度退化成了O(N^2),所以我们有必要对之前的排序方法进行一些改进,使它能够适应包含任何数据的数组。于是就有大佬想出了一个更牛的方法,那就是三路划分快排。
二、三路划分快排的思路
三路划分快排的思路:三路划分,顾名思义就是把数组分成三个部分进行排序,在待排序数组中随机选定一个key,把数组分成小于key的,等于key的,还有大于key的。具体操作是:定义一个left下标为数组首元素下标的前一个位置,即left=begin-1;再定义一个right下标为数组最后一个元素的下标的后一个位置,即right=end+1;再定义一个下标cur=left,作为遍历数组的下标,一共就left,cur,right三个下标。从a[cur]开始与key比较,如果a[cur]<key,就用a[cur]和a[++left]交换(记住这里是先++,后交换),然后++cur;如果a[cur]>key,就用a[cur]和a[- -right]交换(记住这里是先减减,后交换);如果a[cur]==key,那就只++cur即可,这样循环往复,直到cur=right就结束,最后得到的a[begin,left]是比key小的数,a[left+1,right-1]是等于key的数,a[right,end]是大于key的数,划分成三路之后,中间这一路就不用再进行排序了,因为中间这一路都是等于key的,所以已经有序了,只需要对左路和右路再进行递归排序即可。
例如排序以下数组:
动图演示如下:
注意,这里的cur是从begin开始的,制作动图的时候出现了一点偏差。
一趟排序后得到的结果如下,这样就把数组分成了三部分了。
显然,一趟排序之后把数组分成了小于key的,等于key的,还有大于key的。等于key的就不用再操作了,接下来就递归分别对小于key的数和大于key的数进行排序即可。
三、参考代码
//三路划分快排,begin和end是数组的左闭右闭区间,两个端点位置
void ThreeWayQuickSort(vector<int>& a, int begin, int end)
{
//区间不存在就可以返回了
if (begin >= end)
{
return;
}
//需要先定义局部的left和right进行++和--;
//不能直接对begin和end操作,因为排完一趟
//之后需要利用begin和end来确定递归排序的
//边界
int left = begin-1;
int right = end+1;
//随机选key
int key = a[begin+rand()%(end-begin+1)];
int cur = begin;
while (cur < right)
{
//小于就和a[++left]交换,保证小的数在key的左边
if (a[cur] < key)
{
swap(a[++left],a[cur++]);
}
//大于就和a[--right]交换,保证大的数在key的右边
else if (a[cur] > key)
{
//只需要--right,不能cur++,证明看上图
swap(a[--right],a[cur]);
}
else
{
cur++;
}
}
//边界的确定看上图
ThreeWayQuickSort(a, begin, left);
ThreeWayQuickSort(a, right, end);
}
int main()
{
int a[] = { 6,6,6,6,1,2,7,9,3,6,6,10,8 };
int sz = sizeof(a) / sizeof(a[0]);
ThreeWayQuickSort(a, 0, sz - 1);
return 0;
}
以上就是三路划分快排的所有内容了,这个快排可以说是不惧怕任意组合的数据的,无论所给的数组的数据有多么的极端,效率都能很高,这才是最牛的快速排序版本,你学会了吗?如果对你有帮助,那就麻烦您点一点小心心加关注呗,后期还会输出数据结构相关知识哦,我们下期见!!!!