首页 > 编程语言 >简单分治快排问题解析(c++实现)

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

时间:2023-09-17 11:01:40浏览次数:46  
标签:right 题目 nums int 分治 c++ 快排 key left

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

什么是分治快排思想

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

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

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

下面是例题:

题目1:颜色分类

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

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

这道题目就是典型的使用分治思想解决的一道题目,只需要将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)

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

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

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

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

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

下面是代码的实现:

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)

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

这道题目很简单,让我们从数组中返回最小的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];
    }
};

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


标签:right,题目,nums,int,分治,c++,快排,key,left
From: https://blog.51cto.com/u_15838996/7500068

相关文章

  • C++实现论文查重
    软件工程https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13014作业要求根据给出的样例进行查重,并把结果记录在PSP表格中作业目的对查重有一定的初步了解GitHub链接https://github.com/xingch123456789/3119000414PSP表格PSP2.1Person......
  • Qt/C++音视频开发54-视频监控控件的极致设计
    一、前言跌跌撞撞摸爬滚打一步步迭代完善到今天,这个视频监控控件的设计,在现阶段水平上个人认为是做的最棒的(稍微自恋一下),理论上来说应该可以用5年不用推翻重写,推翻重写当然也是程序员爱干的事情,这个就要考验个人的功底,设计的好框架搭建的好,可以很多年不用变,只需要在现有框架小修......
  • C++智能指针
    智能指针是C++语言中一种方便、安全的内存管理工具。智能指针可以自动管理对象的生命周期,避免手动分配和释放内存时可能出现的内存泄漏和悬挂指针等问题。在C++11标准中,引入了三种智能指针:unique_ptr、shared_ptr和weak_ptr。类型含义备注std::unique_ptr 独占资源......
  • C++STL进阶:pb_ds库
    Windows,64bitG++(ISOc20)stack=268435456开启O2优化万能头文件CodeForces在\(\ttC^{20(64)}_{++}\)版本下无法使用bits;如果需要使用priority_queue则无法使用using(会和std撞名字)。#include<bits/extc.h>usingnamespace__gnu_pbds;优先队列(不常用)概述......
  • C++的异常类型与多级catch匹配
    try-catch的用法:try{//可能抛出异常的语句}catch(exceptionTypevariable){//处理异常的语句}我们还遗留下一个问题,就是catch关键字后边的exceptionTypevariable,这节就来详细分析一下。exceptionType是异常类型,它指明了当前的catch可以处理什么类型的异常;varia......
  • C++ 学习笔记、01 | 开发简单职工管理系统遇到的一些问题
    记录开发简单职工管理系统遇到的一些问题,黑马教程https://www.bilibili.com/video/BV1et411b73ZP147~P166头文件与源文件头文件只声明,源文件来实现(本质上是类内声明类外实现)源文件需要引用特定的头文件ifndefOOPFINAL_WORKER_H#defineOOPFINAL_WORKER_H#include<......
  • 合并果子题解-C++ STL priority_queue容器的使用
    说明:本博文关于priority_queue容器的说明来源于www.cnblogs.com/fusiwei/p/11823053.html本人是刚刚接触算法竞赛的萌新,如果有大佬发现了错误,还望指出(真的有人会看本蒟蒻的博文吗)这是我的第一篇博文,更多是作为测试以后会将博客作为笔记记录学习的体会基本概念priority_queu......
  • c++论文查重
    github连接这个作业属于哪个课程软件工程这个作业要求在哪里在这里这个作业的目标了解PSP,写一个论文查重程序,使用github管理项目PSP表PSP2.1PersonalSoftwareProcessStages预估耗时(分钟)实际耗时(分钟)Planning计划2020Estimate估计这个任......
  • C++20起支持的一个小特性
    注释掉的为传统的写法,从C++20起支持default关键字修饰的写法,即使是成员变量有多个的时候也支持,减轻了程序员的心智负担。......
  • C++ STL 编程指北
    C++STL编程指北未避免歧义,所有容器的swap方法和不常用方法均未写1.vector向量容器用一句话来说,vector就是可变长数组。但vector所支持的可变长特性,并不是在原空间之后接续新空间来实现的,因为无法保证之后尚有可供分配的空间。底层实现上当增加新元素时,如果当前vector容......