简单分治快排问题解析(c++实现)

这几天刷了需要使用分治快排思想去解决的几道比较好的题目,所以写下这篇博客用于复习和以后的复盘。

什么是分治快排思想

首先我们要知道什么是分治快排思想,这个思想其实就是在模拟实现qsort算法的时候使用的一个方法,在模拟实现qsort的时候,我们知道第一步是需要使用一个随意选择(三数取中)的方法去选择一个随机数作为key的,然后我们需要遍历待排序的数组,将整个数组分成三个部分分别是小于key的元素,等于key的元素,大于key的元素。那么我们使用的将整个数组分成三个部分的这个思想就是分治快排思想。

其实这个思想还有一个方法名字叫做三向划分数组。这个方法的实现请看下面的图,

下面是例题:

题目1:颜色分类

题目连接:​​75. 颜色分类 - 力扣(LeetCode)​

这道题目就是典型的使用分治思想解决的一道题目,只需要将1选作为基准数然后使用分治算法就可以完成。

代码实现:

class Solution {
public:
    void sortColors(vector<int>& nums) {
        //思路使用三向划分法解决这道题目
        int left = -1;
        int right = nums.size() - 1;
        int i = 0;
        //这道题目其实就是使用两个三个指针将数组划分成了三个部分
        //从o到left这一部分为0
        //从i到right这一部分为1
        //从right+1到最后为最后一部分
        while (i <= right) {
            if (nums[i] == 0)//如果是0属于left部分
            {
                ++left;//让left++
                if (left != i) {//防止自己和自己交换
                    swap(nums[left], nums[i]);
                }
                i++;
            }
            else if (nums[i] == 1)//1就是i处理的部分
            {
                i++;
            }
            else
            {
                swap(nums[i], nums[right]);
                right--;
            }
        }
    }
};

题目2:快速排序

题目链接:​​912. 排序数组 - 力扣(LeetCode)​

这道题目其实有很多种方法解决,我现在写过的方法中使用堆排序,和归并排序都可以通过这道题目,但是如果你要使用快速排序的方法,那么需要注意一个点那就是,在选择key的时候不能使用三数取中法去,因为力扣在后台的测试用例中写了一个特别针对三数取中的方法,会让时间超时,所以要使用随机数取中法。

至于这道题目的思路那就是第一步在数组的元素组使用一次分治,让整个数组变成小于key等于key和大于key的部分,然后再递归去左半部分继续使用分治思想,然后去右半部分递归使用分治,等到最后递归结束的时候数组也就成为有序的数组了。

下面是代码的实现:

class Solution {
public:
    int get_rand(vector<int>&nums,int& left, int& right)
    {
        int ret = (rand()%(right - left+1))+left;
        return nums[ret];
    }//这个函数的功能为产生一个随机数
    void quick_sort(vector<int>& nums,int left,int right)
    {
        if(left>=right)
        {
            return;
        }//确认递归结束的条件
        int key = get_rand(nums,left,right);
        int begin = left-1;
        int cur = left;
        int end = right+1;
        while(cur<end)
        {
            if(nums[cur] == key)
            {
                cur++;
            }
            else if(nums[cur]<key)
            {
                ++begin;
                swap(nums[begin],nums[cur]);
                cur++;
            }
            else
            {
                swap(nums[cur],nums[--end]);
            }
        }
        quick_sort(nums,left,begin);//去到小于key部分元素的递归
        quick_sort(nums,end,right);//去到大于key部分元素的递归
    }
    vector<int> sortArray(vector<int>& nums) {
        srand(time(NULL));//配置随机数种子
        quick_sort(nums,0,nums.size()-1);
        return nums;
    }
};

题目3:数组中第k个最大的元素

题目链接:​​215. 数组中的第K个最大元素 - 力扣(LeetCode)​

题目的要求很简单,让我们去返回数组中第k大的元素,从示例1可以看到,整个数组中最大的元素是6其次就是5,而题目要求我们返回第二大的元素,所以最后返回的数就是5.

下面我们来解释如何解决这道问题,既然要使用分治的方法去解决这道问题。假设这里存在一个长度为n的数组,假设我们这里已经完成了一次分治请看下图:

直到最后也就能够返回答案了。

下面是代码的实现:

class Solution {
public:
    int qsort(vector<int>& nums,int begin,int end,int k)
    {
        if(begin == end)
        {
            return nums[begin];
        }//确定递归结束条件
        //第一步确定一个随机的基准值
        int key = get_rand(nums,begin,end);
        int cur = begin;
        int left = cur-1;
        int right = end+1;
        while(cur<right)
        {
            if(nums[cur]<key)
            {
                swap(nums[++left],nums[cur++]);
            }
            else if(nums[cur] == key)
            {
                cur++;
            }
            else
            {
                swap(nums[--right],nums[cur]);
            }
        }
        int c = end - right +1;//算的大于基准值的元素有多少个
        int b = right - 1 -left;
        if(k<=c)
        {
            return qsort(nums,right,end,k);//代表要求的元素存在于最大的那个部分
        }
        else if(k<=b+c)
        {
            return key;//代表要求的那个元素存在于中间部分
        }
        else
        {
            return qsort(nums,begin,left,k-b-c);//代表要求的那个元素存在于最左边的部分,并且要删除前两个部分对这里的影响
        }
    }
    int findKthLargest(vector<int>& nums, int k) {
       srand(time(NULL));//设定一个随机数种子
       return qsort(nums,0,nums.size()-1,k);//从0到nums.size()-1的元素中寻找第k大的元素 
    }
    int get_rand(vector<int>& nums,int left,int right)
    {
        return nums[rand()%(right - left+1)+left];//返回基准值
    }
};

题目4:最小的k个数

题目链接:​​剑指 Offer 40. 最小的k个数 - 力扣(LeetCode)​

这道题目很简单,让我们从数组中返回最小的k个数,题目的要求很简单,要求我们返回最小的k个数,注意不是第k个最小的数,而是最小的k个数,例子一:就是返回最小的两个数,然后返回的就是2 和 1并且不用处理返回的顺序。

那么这道题目的思路和题目3也就很相似了,依旧是得到a(小于key的元素个数)b(等于key的元素个数)c(大于key的元素个数)三个部分的元素,然后判断a是否大于k如果大于则证明最小的k个元素在a部分中,去到a部分寻找。如果k是小于a+b但是大于a的,那就直接返回答案了,因为此时最小的k个元素正是a所在的所有元素加上b所在的元素,如果是在c部分那不是最小的k个元素,因为前面已经存在了很多个小于当前的c部分内元素的元素,所以在c部分寻找的是k -  a - b的元素。

代码实现:

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& nums, int k) {
        //解决方法:使用快速选择法将前k个数放到nums的前方
        //最后将nums前方的前k个数放到nums即可
        srand(time(NULL));//创建一个随机数种子
        qsort(nums,0,nums.size()-1,k);//函数的目的为将最小的k个元素放到数组开头
        return {nums.begin(),nums.begin()+k}; 
    }
    void qsort(vector<int>& nums,int begin,int end,int k)
    {
        if(begin>=end)
        {
            return;
        }
        //第一步还是获取随机的key
        int key = get_rand(nums,begin,end);
        int cur = begin,left = begin -1,right = end+1;
        while(cur<right)
        {
            if(nums[cur]<key)
            {
                swap(nums[++left],nums[cur++]);
            }
            else if(nums[cur] == key)
            {
                cur++;
            }
            else
            {
                swap(nums[--right],nums[cur]);
            }
        }
        //下面再算出a和b部分的元素数量
        int a = left - begin+1;
        int b = right - 1 - left;
        if(k<a)
        {
            qsort(nums,begin,left,k);//去左半部分继续去寻找
        }
        else if(a+b>=k)
        {
            return;
        }
        else
        {
            qsort(nums,right,end,k-a-b);//去最右部分寻找
        }

    }
    int get_rand(vector<int>& nums,int left,int right)
    {
        return nums[rand()%(right+1-left)+left];
    }
};

因为还是一个在学习的人,文章内容写的不好,请见谅,如果发现了任何错误,请指出,感激不尽。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值