双指针法实现快速排序
思路
快速排序是一种非常高效的排序算法,它的基本思想是“分而治之”。也就是说,如果我们需要对一个数组进行排序,可以先选择一个元素(基准,也就是"pivot")将数组分为两个子数组,一个子数组中的所有元素都比基准小,另一个子数组中的所有元素都比基准大,然后分别对这两个子数组进行排序。如果子数组已经足够小(例如,只包含一个元素),那么这个子数组已经是排序好的。否则,可以用同样的方式继续分割它。
模板示例:
本例子中的快速排序使用的是“Hoare partition scheme”
(霍尔分区方案)
void quickSort(int q[],int l ,int r){
if(l>=r){
return;
}
int i=l-1,j=r+1;
int x=q[l+r>>1]; //在q数组中找到中间值,这里的 >> 是右移运算符,l + r >> 1 等价于 (l + r) / 2
while(i<j){//用于将数组部分分为两个子数组
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j){ //注意此处的边界条件不是<=
swap(q[i],q[j]);
}
}//此时,分出了基于基准的左右两个数组
quickSort(q,l,j); //注意这里是j而不是i,因为j可以保证[l,j]的元素都是<=x的。
quickSort(q,j+1,r);
}
注意点
-
在
“Hoare partition scheme”
中,i
和j
的初始位置是在数组的左右边界的外面,然后在每次循环开始之前,先移动指针,然后再检查元素。所以,我们需要将i
和j
初始化为l - 1
和r + 1
,这样在循环的第一次迭代中,i
和j
就会被移动到数组的左右边界上。 -
x = q[l + r >> 1];
是在选择数组中的一个元素作为基准值(pivot)。这个元素是数组q
的中间元素。这里的>>
是右移运算符,l + r >> 1
等价于(l + r) / 2
。因为在计算机中,右移一位相当于除以2,所以这两个表达式的效果是一样的。 -
do i ++ ; while (q[i] < x);
是将i
向右移动,直到找到一个大于等于x
的元素。do j -- ; while (q[j] > x);
是将j
向左移动,直到找到一个小于等于x
的元素。如果i < j
,那么就交换i
和j
所指向的元素。 -
在进行递归操作的时候,是
quickSort(q,l,j)
而不是quickSort(q,l,i)
的原因:当i
和j
相遇时,j
指向的位置可以确保是小于等于基准值的。因此,在递归调用quick_sort(q, l, j)
时,我们知道子数组l
到j
包含的元素都是小于等于基准值的。而在递归调用quick_sort(q, j + 1, r)
时,我们知道子数组j + 1
到r
包含的元素都是大于等于基准值的。
为什么if后的条件是i<j而不是i<=j?
在快速排序的分区操作中,i
和 j
两个指针会分别从数组的两端开始搜索,直到找到需要交换的元素。当 i
和 j
相遇时,表示搜索结束。
如果使用 i <= j
作为交换条件,那么在 i
和 j
相遇的时候,程序可能会交换一个元素与自身,虽然这不会影响排序的结果,但是这是一个不必要的操作。
更重要的是,当 i
和 j
相遇时,我们需要确定这个位置在下一次递归中属于哪个子数组。在“Hoare partition scheme”
中,我们通常会选择 j
作为划分点,因为我们知道 j
指向的位置的元素是小于等于基准值的,所以 j
及其左边的元素应该属于左边的子数组,而 j + 1
及其右边的元素应该属于右边的子数组。
如果我们使用 i <= j
作为交换条件,那么在 i
和 j
相遇时,我们不能确定 i
或 j
指向的元素是小于还是大于基准值的,所以我们不能确定这个位置应该属于哪个子数组。因此,我们通常会使用 i < j
作为交换条件,这样可以更清晰地划分子数组。
do-while循环
do...while
是一种后测试循环结构,也就是说,它先执行循环体(do
后面的部分),然后检查循环条件(while
后面的部分)。只要循环条件为真,就会重复执行循环体。如果循环条件为假,就会退出循环。
以快速排序代码为例, do i++; while(q[i] < x);
这个循环:
- 首先,执行
i++
,将i
加1。 - 然后,检查
q[i] < x
,也就是看数组q
在新的i
位置上的元素是否小于基准值x
。 - 如果
q[i] < x
为真,那么会再次执行i++
,然后再次检查q[i] < x
。 - 如果
q[i] < x
为假,那么就退出循环。
这个循环的作用是将 i
向右移动,直到找到一个不小于 x
的元素。
同理,do j--; while(q[j] > x);
这个循环的作用是将 j
向左移动,直到找到一个不大于 x
的元素。
这两个循环都会在找到需要交换的元素或者 i
和 j
相遇时结束。
有序数组平方和:快速排序暴力解法
class Solution {
public:
void quickSort2(vector<int>& nums,int l,int r) {
if (l >= r) {
return;
}
int i = l - 1;
int j = r + 1;
int x = nums[l + (r - l) / 2]; //注意这里并不是中间值下标,应该为中间值本身!
while (i < j) {
do i++; while (nums[i] < x); //划分左右两个以x为分界线的数组
do j--; while (nums[j] > x);
if (i < j) {
std::swap(nums[i], nums[j]);
}
}
//划分为两个数组之后,进行递归
quickSort2(nums, l, j);
quickSort2(nums, j + 1, r);
}
vector<int> sortedSquares(vector<int>& nums) {
for(int i=0;i<nums.size();i++){
nums[i] *= nums[i];//平方运算
}
quickSort2(nums,0,nums.size()-1);
//sort(nums.begin(),nums.end());//快速排序库函数
return nums;
}
};
注意x应该是中间值本身而不是下标