目录标题
对快排模板简单的理解和几个注意点的原理
源码:
\qquad 先直接贴代码,对快排本身有一定理解的同学可以直接看注意点的注释
class Solution {
public:
void quicksort(vector<int>& nums,int l,int r){
if(l>=r)return;
int pivot=nums[l+(r-l)/2],i=l-1,j=r+1;
while(i<j){
while(nums[++i]<pivot);
while(nums[--j]>pivot);//后++后--会让交换时的ij变化,使交换错误兄弟
if(i<j)swap(nums[i],nums[j]);
}
quicksort(nums,l,j);//选j因为取中值时l+(r-l)/2偏左且不会取到r AMD,yes!
quicksort(nums,j+1,r);//j+1因为j=i-1或j==i
}
vector<int> sortArray(vector<int>& nums) {
quicksort(nums,0,nums.size()-1);
return nums;
}
};
基本思路
\qquad
分治法,每次递归要保证完成分治点左边均小于等于pivot,右边均大于等于pivot
举例:
初始,这图漏了数组元素:5,1,1,2,0,0,pivot=1;
\qquad
第一次进大while循环两个小while之后i,j停下的位置,这图漏了标交换,这次是交换0和5(T▽T),没事下个图就标有了
\qquad
第二次进while循环两个小while之后i,j停下的位置,
\qquad
第三次就i,j相遇了,此时j的左边都小于等于pivot,右边都大于等于pivot,至此这次函数的基本使命就完成了,接下来就是左右分配任务递归了
(函数:我滴任务,完成力!------开始传承接下来每个函数都干同样的事,直到传进的l>=r 便结束递归不再传承)
模板中几个注意点
1. pivot的取值
\qquad 一般来说初学接触到的多是pivot=nums[l],也就是取最左边。不过我个人喜欢直接取中值偏左,中值可以较大概率的对题目中数据尽可能进行均匀分区(分区:上图绿色部分的分区,也是接下来递归函数需要执行的区域) 当然还是有其他取值方式,比如靠运气决定运行速度的随机化取值
srand((unsigned)time(NULL));
int random_number=rand()%(right-left+1)+left;
int pivot=nums[random_number]
再比如三值取中法:取首位值和中值和末尾值的平均值
即
// 首位值 中值 末位值
int pivot=(nums[l]+nums[l+(r-l)/2]+nums[r])/3;
2. i和j的取值为什么是l-1和r+1
\qquad 我也曾想为什么不能直接 i=l,j=r; 然后后面的小while里用
while(nums[i++]<pivot);
while(nums[j--]>pivot);
但是后++后–的方式会使 i 和 j 在不满足小while的条件要退出后多执行一次++和–,这影响了后面交换使用的 i 和 j ,好点的会漏几个数没排,大概率是直接访问到数组外去了
什么?你问为什么小while一定要改成后++?不可以还是前++?呃,可以,但你nums[l]和nums[r]还是要判断的吧,所以会需要类似特判的,在while之前先判断一次[ l ]和[ r ],如果你觉得好记的话,也可以
\qquad 所以不如从了我吧(bushi),咱们为了之后的幸福各退一步往两边取值(〃>_<;〃)
3. 小while里的[++i]和[–j]
\qquad 嗯,其实跟上面那个疑问的解答本质是一样的,都是为了完整遍历范围内的元素,不想前++的解决方案也一样,你可以先处理nums[ l ]和nums[ r ]看需不需要将指针停下,但是要注意和之后的小while里配合好,避免漏数
4. 递归为什么一定要 j,不可以 i 吗
\qquad
不可以,起码在这套模版一点不改的情况下不可以,会出现边界问题,因为我们在取pivot值时会影响到最后递归的取值。
\qquad
什么情况下会出现边界问题?
当pivot取nums[l]时(可以看成取i),递归还是取了[l,i-1]和[i,r]
当pivot取nums[r]时(可以看成取j),递归还是取了[l,j]和[j+1,r]
什么是边界问题:
此时会无限递归[0,1]区间
所以呀(˘•ω•˘)
当pivot取得到nums[l]时递归只能从[l,j]和[j+1,r]
当pivot取得到nums[r]时递归只能从[l,i-1]和[i,r]
\
掺点私货
生命因何而沉睡?诸君,共勉!
就酱,再见!