基于快排的快速选择算法

基于快排的快速选择算法

  1. 问题

    最近做到一个经典的leetcode题目,由于分类在“分治算法”标签中,故思考用分治的思想解决,题目如下,

    剑指offer40.最小的k个数

    输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

    示例 1:

    输入:arr = [3,2,1], k = 2 输出:[1,2] 或者 [2,1]

    示例 2:

    输入:arr = [0,1,2,1], k = 1 输出:[0]

    本题有三种解法,分别是简单粗暴的排序然后取前k个数,基于堆排序的一种改进算法,以及本文所讲的快速选择算法。第一种方法实际上是将数组中的所有元素先排序,然后取前k个,但是我们想真的有必要将所有元素一次性的进行排序吗?我们只需要知道最小的k个元素即可,所以我们是否可以对部分元素进行排序,然后判断再对部分元素进行排序,一旦达到我们的目标(已经筛选出k个元素了),全身而退。理论上这是要比一次性排序所需的时间少得多的。第二种和第三种方法都带有这类思想。

  2. 回忆快速排序

    在快排中也是运用了分治的思想,每一次排序中,我们会在数组中选择一个元素作为枢轴(可以理解为杠杆点,一般选择数组第一个点,当然也可以随机选择),通过双指针low,high将比这个枢轴元素小的元素放在它的左侧,比它大的元素放在它的右侧。这样就完成了一次初步的排序,以这个枢轴为中心点可以划分成左右两块,左边的元素都比枢轴小,右边的元素都比枢轴大。进而对左右两个子数组再次进行以上的步骤,这也体现了分而治之的思想。具体过程看下图,

    基于快排的快速选择算法_第1张图片

快速排序核心代码如下,

 while(lo < hi){
            while(lo= pivotkey)hi--;
            arr[lo] = arr[hi];
            while(lo
  1. 快速选择的原理

    快速选择就是在上述基础上进行改造的,我们可以看到,上图中已经通过一个杠杆点将数组分成了左右两块,称为左块和右块,左块的元素肯定比杠杆点小或等于,那么因为题目中是要求我们选取最小的k个点,我们假设k等于4,那么上图中已经达到了我们的目标,直接返回杠杆点的下标即可。也就是low+1-start == k即返回low,其中start为low刚进来的值,因为low的初值不一定都是0,用lo+1-start表示左块的元素个数。

    下面考虑若low-start+1 < k,这说明左块的元素数量还不够,但是左块中的元素和杠杆点肯定已经是最小k个元素中的了,所以我们只需要递归一次,对lo+1,nums.size()-1区间上的元素再找出 k-(low+1-start),就可以返回这个区间上的杠杆点的坐标即为最终的结果。

    low-start+1 > k,这说明左块的元素太多了,那么我们就可以完全舍弃右块,在区间start,lo-1上找到k个最小元素即可。

    这有点像一种变相的分治,即分完之后并不对每一个块进行治理,而是有选择的进行治理。

  2. 快速选择的代码实现
int Partition(vector& arr,int lo,int hi,int k){
        if(hi - lo + 1 == k )return hi;    //注意:若传进来区间的长度已经等于k了,那么没必要快排了,直接返回即可。
        int start = lo;    //不然很难选中一个刚刚好的杠杆点,从而会造成反复横跳的情况。
        int pivotkey = arr[lo];
        while(lo < hi){        //快排核心代码
            while(lo= pivotkey)hi--;
            arr[lo] = arr[hi];
            while(lo

你可能感兴趣的:(算法快速排序leetcode)