快排基本思路:
- 主元选择,这里直接选择第一个元素作为主元,可能导致时间复杂度为O(n),通常会从begin,mid,end选择中间元素做主元,同时对这三个元素进行排序(类似希尔排序)
- 双指针,将主元放在一边,这里将主元放在第一个元素(即位置不变),两个指针start和last,start为从begin+1开始从前往后走的指针,last为从end开始从后往前的指针。
- 遍历,start找到第一个大于主元的元素,end找到第一个小于主元的元素,交换两者位置。循环直到start大于last。
- 主元归位,跳出循环后,找到主元应该在的位置。
- 递归,对主元前的数据段进行快排,主元后的数据段进行快排
快排实现关键点
- 当指针进行遍历时需要注意三种边界情况:1. 所有元素都比主元小。2. 所有元素都比主元大。3. 当指针碰到和主元一样大的元素时是否交换。
- 主元归位是和last进行交换的原因:last最终停下来的位置一定是小于s[pivot]的位置或者就是pivot的位置(若与start交换可能因为,start停下来的位置是因为start==end)
- 快排快的原因:每经历一次递归调用,该调用中的主元的位置都会确定,并不会在后续的调用中发生改变。
void quicksort(vector<int>& nums, int begin, int end)
{
if(begin<end)
{
int pivot = nums[begin];//取第一个元素为主元
swap(nums[begin], nums[end]);
int idx=begin;
for(int i=begin;i<end;++i)
{
if(nums[i]<pivot)
swap(nums[i], nums[idx++]);
}
swap(nums[idx], nums[end]);
quicksort(nums, begin, idx-1);
quicksort(nums, idx+1, end);
}
}
STL中的sort源码
其中几个tips
- 快排是一个递归调用,为了防止栈被被过度使用(顺便提一下堆、栈、静态区的区别),所以要限制递归调用的深度
- 为了递归调用的深度尽量小,所以要先处理主元前后短一点的数据段,因为先处理长一点的数据的话又会被更多次递归调用,加深递归深度。
- sort总共涉及堆排序、快速排序、插入排序,
总结
源码中_ISORT_MAX是常量32,排序长度大于32时,会先进行快速排序,并用_Ideal参数限制快速排序递归调用的深度。每次while循环都会执行_Ideal = _Ideal*3/4。由于初始_Ideal值为排序长度N,所有限制的深度实际为Log(3/4)N。
当进行完一次快速排序,会将主元前后更短的数据进行递归调用,而更长的数据段用while循环进行处理
当多次递归调用快速排序且Ideal减小为0,若此时需要排序的数据段长度仍大于32时,就会调用堆排序,若在[2,32]之间,就会使用插入排序
个人的一些观点,有错误欢迎指正