思路:数组nums,首元素nums[0]
设置头尾两个指针i, j 分别指向第二个元素和最后一个元素。
不断向右移动i,找到第一个大于nums[0]的元素;
不断向左移动j,找到第一个小于nums[0]的元素;
将nums[i]和nums[j]互换。
重复上述过程直到i>j, 然后将此时的nums[0]和nums[j]互换。
分别将j左边的一组数和右边的一组数分别进行上述递归。
但是我们一般情况下写的这种两段的快排是有问题的,比如说当数组是0111111111111111111111111112这种情况复杂度就会直接退化成O(n^2)。所以正确的快排应该写成三段,大于nums[0],等于nums[0],小于nums[0]三段,而且不能总选取数组第一个元素进行比较,而是应该随机选取这个点。
思路1:最开始分区的过程,我是一个一个区来划分的,这种写法比较挫,还是不删了,放着做纪念吧。
void Sortdfs(vector<int>&nums, int first, int last) { if(first<last) { int a=first+rand()%(last-first+1); swap(nums[first],nums[a]); int i=first+1, j=last; //分成小于等于和大于两段 while(true) { while(i<=j && nums[j]>nums[first]) j--; while(i<j && nums[i]<=nums[first]) i++; if(i<j) swap(nums[i],nums[j]); else break; } //first到j小于等于,j+1到last大于 //分成小于和等于两段 int p=first+1, q=j; while(true) { while(p<=q && nums[q]==nums[first]) q--; while(p<q && nums[p]<nums[first]) p++; if(p<q) swap(nums[q],nums[p]); else break; } swap(nums[first], nums[q]); //first到q-1小于,q到j等于 Sortdfs(nums, j+1, last); Sortdfs(nums, first, q-1); } }
思路2:通过力扣(75、分类颜色)这道题得到了启发,扫一遍直接实现分区,优雅地实现了三路快排。也祝愿大家都在追求优雅极致的代码的路上越走越远!
分区思路:设置两个指针,l记录第一个nums[a]的位置,l左边比nums[a]小,r记录最后以个nums[a]的位置,r右边比nums[a]大。
然后使用i从头到尾扫一遍,直到与r相遇。
i遇到比nums[a]小的数就换到左边去,遇到比nums[a]大的数就换到右边去,遇到nums[a]就跳过。
需要注意的是:
1、当遇到比nums[a]大的数就换到右边去,换回来的可能是比nums[a]小的数(或者nums[a]),i不能前进,因为i前面可能会有nums[a],要后续判断;
2、当遇到比nums[a]小的数就换到左边去,i每次都要前进,不能等待后续判断,因为l会前进,但是i却不动的话,l会跑到i前面,出错。
那为什么这里不需要后续判断呢?如果换回来的是比nums[a]小的数,那说明[0,l]区间内都是比nums[a]小的数,i前面不会有nums[a],l和i往前进即可。
上述两种情况如果换回来的是nums[a],就没啥可说的了,主要就是怕出现比nums[a]小的数前面有nums[a]的情况。
由此该数组分为4段:[0,left)-->比nums[a]小的数; [left,i)-->nums[a]; [i,right]-->乱序; (right,n-1]-->比nums[a]大的数
void quick_sort2(vector<int>&nums, int first, int last) { if (first<last) { int a = first + rand() % (last - first + 1), aa = nums[a]; int l = first, r = last, i = first; while (i <= r){ if (nums[i] > aa){ swap(nums[i], nums[r]); --r; } if (nums[i] == aa) ++i; if (i <= r && nums[i] < aa){ swap(nums[i], nums[l]); ++l, ++i; } } quick_sort2(nums, first, l-1); quick_sort2(nums, r+1, last); } }