快速排序 c++

快速排序

已知一个数组 nums = [50, 10, 90, 30, 70, 40, 80, 50, 20,我们需要对这个数组进行排序,因此我们选择了 快速排序 进行排序。

快速排序的思想:选取一个 pivot(哨兵),一般是待排序的数组的第一个元素,将数组分割成两部分,使得前面一部分小于等于 pivot,后面一部分大于等于 pivot;之后再对这两个部分分别进行快速排序。
如下图所示,其中绿色部分小于等于 pivot,红色部分大于等于红色。在这里插入图片描述

快速排序的流程 partition(& nums, low, high)
1、pivot、i 和 j 的定义,pivot=nums[low]; i=low; j=high
2、循环条件:i < j
① 从右往左找,nums[j] >= pivotj--;若找到一个比 pivot 小的元素,记录此时不满足条件的下标 j;
② 从左往右找,nums[i] <= pivoti++;若找到一个比 pivot 大的元素,记录此时不满足条件的下标 i;
③ 交换 i 和 j 对应的元素,swap(nums[i], nums[j])
④ 直到 i == j,跳出循环;
3、将交换 nums[i] 与 pivot 进行交换,swap(nums[i], nums[low])
4、对绿色部分的进行快速排序,递归调用 partition(nums, low, j-1) 方法;
5、对红色部分的进行快速排序,递归调用 partition(nums, j+1, high) 方法;

  • i == j 时,i(或者 j)是要与 pivot 进行交换的;并且此时只有 i 的位置是确定的,不会随着后续的快速排序而发生改变,所以可以根据此时的 i 索引得出 nums[i] 是第 k 小(从小到大排序)。
  • 为什么 i==j 要停止循环?因为当 i 和 j 相遇时,说明已经遍历完所有的元素,此时所有的交换已经完成,此时再继续移动指针已经没有意义。此外,在 i 和 j 相遇时,j 应该指向的是左侧区域最后一个小于 pivot 的元素或者 pivot 自身,i 指向的是右侧第一个大于 pivot 的元素或者自身。
  • 上述的流程中,必须先 --j++i。因为要保证 pivot 的正确位置,如果先移动 i,nums[i] 会移动到一个等于或大于 pivot 的元素,这时 nums[i] 可能与 pivot 值相等,而后续的 j 往前移动的话,可能没有机会找到小于 pivot 的元素。先减少 j 的索引,可以确保右侧区域的元素都比 pivot 大或等于 pivot,顺利找到小于 pivot 的元素。
    假设有个数组 [5, 5, 3, 7],如果先进行 i++ 再进行 j--,则 i 首先从 5 不间断地移动到 7,最终 i ==j 循环退出,而中间的 3 就不会被正确交换。
class Solution {
public:
    void quickSort(vector<int>& nums) {
        partition(nums, 0, nums.size()-1);
        // 打印数组
    }

    void partition(vector<int>& nums, int low, int high) {
        if (low >= high) return;

        int i = low, j = high, pivot = nums[low];
        while (i < j) {
            while (i < j && nums[j] >= pivot) j--;
            while (i < j && nums[i] <= pivot) i++;

            if (i < j) swap(nums[i], nums[j]); 
        }
        swap(nums[low], nums[j]);

        partition(nums, low, i-1);
        partition(nums, i+1, high);
    }
};
力扣:数组中的第K个最大元素

215. 数组中的第K个最大元素
根据题意,我们需要对数组进行排序才能知道第 K K K 大的元素,因此最简单的解法是整个元素进行排序,然后取第 K K K 大(数组从大到小排序)或者第 n − K n-K nK 大(数组从小到大排序,其中 n 是数组长度)的元素。

这里我们使用快速排序对数组进行排序,进行快速选择

每一次排序完,我们都能确定一个元素的索引 idx = i = j,将这个 idx K K K 比较:若一致,则找到这个第 K K K 大或者第 n − K n-K nK 大的元素;若不一致,则从根据 K K K 和 idx 的关系,判断出第 K K K 大在[left, idx-1] 范围内还是在[idx+1, right] 范围内,然后另一边的范围我们可以直接忽略掉。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        return partition(nums, nums.size() - k);   // 第 n-K 大 
    }

    int partition(vector<int>& nums, int k) {
        int le = 0, ri = nums.size() - 1;

        while(true) {
            int i = le, j = ri;
            int idx = rand() % (ri - le + 1) + le; 
            swap(nums[le], nums[idx]);  // 哨兵放在第一位
            
            while (i < j) {
                while (i < j && nums[j] >= nums[le]) j--;
                while (i < j && nums[i] <= nums[le]) i++; 

                if (i < j) swap(nums[i], nums[j]);
            }

            swap(nums[i], nums[le]);      // 将相遇点与哨兵元素交换

            if (i == k) return nums[i];  
            else if(i < k) le = i + 1;    // 第 k 小的元素在右半部分
            else if(i > k) ri = i - 1;
        }
        
    }
};
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值