双指针法实现快速排序

双指针法实现快速排序

思路

​ 快速排序是一种非常高效的排序算法,它的基本思想是“分而治之”。也就是说,如果我们需要对一个数组进行排序,可以先选择一个元素(基准,也就是"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”中,ij 的初始位置是在数组的左右边界的外面,然后在每次循环开始之前,先移动指针,然后再检查元素。所以,我们需要将 ij 初始化为 l - 1r + 1,这样在循环的第一次迭代中,ij 就会被移动到数组的左右边界上。

  • 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,那么就交换 ij 所指向的元素。

  • 在进行递归操作的时候,是quickSort(q,l,j)而不是quickSort(q,l,i)的原因:当 ij 相遇时,j 指向的位置可以确保是小于等于基准值的。因此,在递归调用 quick_sort(q, l, j),我们知道子数组 lj 包含的元素都是小于等于基准值的。而在递归调用 quick_sort(q, j + 1, r),我们知道子数组 j + 1r 包含的元素都是大于等于基准值的。

为什么if后的条件是i<j而不是i<=j?

在快速排序的分区操作中,ij 两个指针会分别从数组的两端开始搜索,直到找到需要交换的元素。ij 相遇时,表示搜索结束。

如果使用 i <= j 作为交换条件,那么在 ij 相遇的时候,程序可能会交换一个元素与自身,虽然这不会影响排序的结果,但是这是一个不必要的操作。

更重要的是,当 ij 相遇时,我们需要确定这个位置在下一次递归中属于哪个子数组。在“Hoare partition scheme”中,我们通常会选择 j 作为划分点,因为我们知道 j 指向的位置的元素是小于等于基准值的,所以 j 及其左边的元素应该属于左边的子数组,而 j + 1 及其右边的元素应该属于右边的子数组。

如果我们使用 i <= j 作为交换条件,那么在 ij 相遇时,我们不能确定 ij 指向的元素是小于还是大于基准值的,所以我们不能确定这个位置应该属于哪个子数组。因此,我们通常会使用 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 的元素。

这两个循环都会在找到需要交换的元素或者 ij 相遇时结束。

有序数组平方和:快速排序暴力解法

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应该是中间值本身而不是下标

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速排序可以使用双指针实现。双指针是通过两个指针i和j分别从数组的两端开始搜索,直到找到需要交换的元素。当i和j相遇时,表示搜索结束。 具体实现步骤如下: 1. 选择一个基准元素,可以是数组的第一个元素或者随机选取一个元素作为基准元素。 2. 初始化两个指针i和j,分别指向数组的起始位置和末尾位置。 3. 从右向左移动指针j,直到找到一个小于等于基准元素的元素。 4. 从左向右移动指针i,直到找到一个大于等于基准元素的元素。 5. 如果i小于等于j,则交换指针i和指针j所指向的元素。 6. 继续执行步骤3到步骤5,直到i大于j。 7. 将基准元素与指针j所指向的元素交换位置,此时基准元素的位置已经确定。 8. 对基准元素左边的子数组和右边的子数组分别进行递归排序,直到子数组的长度为1或者0。 下面是一个使用双指针实现快速排序的示例代码: ```python def quick_sort(arr, low, high): if low < high: pivot_index = partition(arr, low, high) quick_sort(arr, low, pivot_index - 1) quick_sort(arr, pivot_index + 1, high) def partition(arr, low, high): pivot = arr[low] i = low + 1 j = high while True: while i <= j and arr[i] < pivot: i += 1 while i <= j and arr[j] > pivot: j -= 1 if i <= j: arr[i], arr[j] = arr[j], arr[i] else: break arr[low], arr[j] = arr[j], arr[low] return j # 示例用 arr = [5, 2, 8, 9, 1, 3] quick_sort(arr, 0, len(arr) - 1) print(arr) # 输出:[1, 2, 3, 5, 8, 9] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值