首页 > 编程语言 >C++优选算法 分治-快排

C++优选算法 分治-快排

时间:2024-11-02 20:46:10浏览次数:6  
标签:right cur nums int 分治 C++ 快排 数组 left

一、基本思想

快速排序采用分治法策略,将一个大数组(或子数组)分为两个小数组,然后递归地对这两个小数组进行排序。其基本思想可以概括为“分解、解决、合并”三个步骤:

  1. 分解:将原问题(即待排序的数组)分解为若干个规模较小、相互独立且与原问题形式相同的子问题(即子数组)。
  2. 解决:若子问题规模较小而容易被解决(即子数组的长度较小),则直接进行排序;否则,递归地解各个子问题。
  3. 合并:由于快速排序是就地排序(即在原数组上进行排序,不需要额外的存储空间),所以子数组排序完成后,原数组也就排序完成了,不需要额外的合并步骤。

二、具体实现

快速排序的具体实现包括以下几个关键步骤:

  1. 选择基准:从待排序序列中选择一个元素作为基准(Pivot)。基准的选择可以影响排序的效率,常见的选择方法包括选择第一个元素、最后一个元素、中间元素或随机选择。
  2. 分区:通过一趟扫描,将数组分为两个部分。在分区完成后,基准元素将处于其最终位置(即左侧的元素都小于等于基准,右侧的元素都大于基准)。分区过程通常使用两个指针(i和j)来进行:i指针从左到右扫描数组,找到第一个大于基准的元素;j指针从右到左扫描数组,找到第一个小于等于基准的元素。然后交换i和j指向的元素,直到i和j相遇或交错。
  3. 递归排序:对基准元素左侧和右侧的子数组进行递归调用快速排序。

 三、示例题目

1.颜色分类. - 力扣(LeetCode)

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

解法(快排思想-三指针法使数组分三块):
算法思路:

类比数组分两块的算法思想,这里是将数组分成三块,那么我们可以再添加一个指针,实现数组分三块。
设数组大小为 n,定义三个指针 left,cur,right:

  • left:用来标记 0 序列的末尾,因此初始化为 -1;
  • cur:用来扫描数组,初始化为0;
  • right:用来标记 2 序列的起始位置,因此初始化为n。

在cur 往后扫描的过程中,保证:

  • [0,left]内的元素都是 0;
  • [left +1,cur -1]内的元素都是 1;
  • [cur,right-1]内的元素是待定元素。
  • [right,n]内的元素都是 2。

算法流程:
a.初始化cur =0,left =-1,right = numsSize;
b.当 cur < right 的时候(因为 right 表示的是 2 序列的左边界,因此当 cur碰到right 的时候,说明已经将所有数据扫描完毕了),一直进行下面循环:

根据 nums[cur]的值,可以分为下面三种情况:

  • nums[cur]== 0;说明此时这个位置的元素需要在 left +1的位置上,因此交换 left +1与 cur 位置的元素,并且让 left++(指向 0 序列的右边界),(为什么可以 ++呢,是因为 left +1 位置要么是0,要么是 cur ,交换完毕之后,这个位置的值已经符合我们的要求,因此cur++);
  • nums[cur]== 1;说明这个位置应该在 left 和 cur 之间,此时无需交换,直接让cur++,判断下一个元素即可;
  • nums[cur]== 2;说明这个位置的元素应该在 right -1 的位置,因此交换right-1与cur 位置的元素,并且让right-- (指向 2 序列的左边界)cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)

c.当循环结束之后:

  • [0,left]表示0序列;
  • [left + 1, right-1]表示 1 序列;
  • [right, numsSize-1]表示 2 序列 。
    class Solution {
    public:
        void sortColors(vector<int>& nums) 
        {
            int n=nums.size();
            int cur=0,left=-1,right=n;
            while(cur<right)
            {
                if(nums[cur]==0)
                {
                    swap(nums[++left],nums[cur++]);
                }
                else if(nums[cur]==1)
                {
                    cur++;
                }
                else if(nums[cur]==2)
                {
                    swap(nums[--right],nums[cur]);
                }
            }
        }
    };

    2.快速排序. - 力扣(LeetCode)

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

解法(数组分三块思想+随机选择基准元素的快速排序)

算法思路:
快排最核心的一步就是 Partition(分割数据):将数据按照一个标准,分成左右两部分。

如果我们使用荷兰国旗问题的思想,将数组划分为 左中右 三部分:左边是比基准元素小的数据,中间是与基准元素相同的数据,右边是比基准元素大的数据。然后再去递归的排序左边部分和右边部分即可(可以舍去大量的中间部分)

在处理数据量有很多重复的情况下效率会大大提升。
算法流程:

随机选择基准算法流程:
函数设计:int randomKey(vector<int>& nums, int left, int right)

  • a.在主函数那里种一颗随机数种子;
  • b.在随机选择基准函数这里生成一个随机数;
  • c.由于我们要随机产生一个基准,因此可以将随机数转换成随机下标: 让随机数 % 上区间大小,然后加上区间的左边界即可。

快速排序算法主要流程:

  • 定义递归出口; 
  • 利用随机选择基准函数生成一个基准元素;
  • 利用荷兰国旗思想将数组划分成三个区域;
  • 递归处理左边区域和右边区域。
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        srand(time(nullptr));//种下一个随机数种子
        qsort(nums,0,nums.size()-1);
        return nums;
    }

    void qsort(vector<int>&nums,int l,int r)
    {
        if(l>=r)
            return;

        //数组分三块
        int key=getRand(nums,l,r);
        int i=l,left=l-1,right=r+1;
        while(i<right)
        {
            if(nums[i]<key)
            {
                swap(nums[i++],nums[++left]);
            }
            else if(nums[i]>key)
            {
                swap(nums[i],nums[--right]);
            }
            else
                i++;
        }
        //[l,left]  [left+1,right-1]  [right,r]
        qsort(nums,l,left);
        qsort(nums,right,r);
    }
    int getRand(vector<int>&nums,int left,int right)
    {
        int r=rand();
        return nums[r%(right-left+1)+left];
    }
};

 3.快速选择算法

数组中的第K个最大元素. - 力扣(LeetCode)

 

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4],k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

解法(快速选择算法)

算法思路:
在快排中,当我们把数组「分成三块」之后:[l,left]   [left + 1,right - 1]   [right,r],我们可以通过计算每一个区间内元素的「个数」,进而推断出我们要找的元素是在「哪一个区间」里面。
那么我们可以直接去「相应的区间」去寻找最终结果就好了。 

class Solution {
public:
     int findKthLargest(vector<int>& nums, int k) {
        srand(time(nullptr));
        int ret = getKMax(nums, 0, nums.size() - 1, k);
        return ret;
    }

    int getKMax(vector<int>& nums, int l, int r, int k)
    {
        if (l == r)
            return nums[l];
        int key = getRand(nums, l, r);

        int i = l, left = l - 1, right = r + 1;
        while (i < right)
        {
            if (nums[i] < key)
            {
                swap(nums[i++], nums[++left]);
            }
            else if (nums[i] > key)
            {
                swap(nums[i], nums[--right]);
            }
            else
            {
                i++;
            }
        }

        //分情况讨论
        if (r - right+1 >= k)
        {
            return getKMax(nums, right, r, k);
        }
        else if (r - left >= k)
        {
            return key;
        }
        else
        {
            return getKMax(nums, l, left, k - (r - left));
        }
    }

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

 4.库存管理Ⅲ. - 力扣(LeetCode)

仓库管理员以数组 stock 形式记录商品库存表,其中 stock[i] 表示对应商品库存余量。请返回库存余量最少的 cnt 个商品余量,返回 顺序不限

示例 1:

输入:stock = [2,5,7,4], cnt = 1
输出:[2]

示例 2:

输入:stock = [0,2,3,6], cnt = 2
输出:[0,2] 或 [2,0]

解法(快速选择算法)
算法思路:

在快排中,当我们把数组「分成三块」之后:[l,left]   [left +1,right - 1]   [right,r],我们可以通过计算每一个区间内元素的「个数」,进而推断出最小的k个数在哪些区间里面。
那么我们可以直接去「相应的区间」继续划分数组即可。 

class Solution {
public:
    vector<int> inventoryManagement(vector<int>& nums, int k) 
    {
        srand(time(NULL));
        getKmin(nums,0,nums.size()-1,k);
        return {nums.begin(),nums.begin()+k};
    }

    void getKmin(vector<int>&nums,int l,int r,int k)
    {
        if(l>=r)
            return;
        int key=getRand(nums,l,r);

        int left=l-1,right=r+1,i=l;
        while(i<right)
        {
            if(nums[i]<key)
            {
                swap(nums[i++],nums[++left]);
            }
            else if(nums[i]>key)
            {
                swap(nums[i],nums[--right]);
            }
            else
            {
                i++;
            }
        }
        int a=left-l+1,b=right-left-1;
        if(a>k)
        {
            getKmin(nums,l,left,k);
        }
        if(a+b>=k)
        {
            return;
        }
        else
        {
            getKmin(nums,right,r,k-a-b);
        }
    }

    int getRand(vector<int>&nums,int l,int r)
    {
        return nums[rand()%(r-l+1)+l];
    }
};

 

标签:right,cur,nums,int,分治,C++,快排,数组,left
From: https://blog.csdn.net/m0_74826386/article/details/143455294

相关文章

  • C++ 手撕--共享式智能指针(shared_ptr)的简单实现
    C++手撕--共享式智能指针(shared_ptr)的简单实现共享式智能指针(shared_ptr):#include<iostream>#include<mutex>usingnamespacestd;template<typenameT>classShared_ptr{private:T*ptr;int*ref_count;std::mutex*mtx;voidrelease(){......
  • C++ 创建动态二维数组
    方法一:使用vector容器1.创建整数型二维数组先建列,再建行,先创建一个包含m个元素的向量a,再创建二维向量arr,通过push_back将一维向量作为行添加到二维向量中#include<iostream>#include<vector>usingnamespacestd;intmain(){ //创建一个包含m个元素,每个元素初始值为0......
  • 打卡信奥刷题(159)用C++工具信奥P1416[普及组/提高] 攻击火星
    攻击火星题目描述一群外星人将要攻击火星。火星的地图是一个nnn个点的无向图。这伙外星人将按照如下方法入侵,先攻击度为0......
  • 【C++动态规划】有效括号的嵌套深度
    本文涉及知识点C++动态规划LeetCode1111.有效括号的嵌套深度有效括号字符串定义:对于每个左括号,都能找到与之对应的右括号,反之亦然。详情参见题末「有效括号字符串」部分。嵌套深度depth定义:即有效括号字符串嵌套的层数,depth(A)表示有效括号字符串A的嵌套深度。详......
  • 【GESP】C++一级练习BCQM3149,重复说话
    GESP一级知识点for循环语句和输出语句,非常简单。题目题解详见:https://www.coderli.com/gesp-1-bcqm3149/【GESP】C++一级练习BCQM3149,重复说话|OneCoderGESP一级知识点for循环语句和输出语句,非常简单。https://www.coderli.com/gesp-1-bcqm3149/C++GESP专项交流频道:GESP......
  • C++ 实现俄罗斯方块游戏
    ✅作者简介:2022年博客新星第八。热爱国学的Java后端开发者,修心和技术同步精进。......
  • C和C++的字符串有什么不同?
    C字符串C语言没有专门用于存储字符串的变量类型,字符串都被存储在char类型的数组中,且以字符 \0结尾;#include<stdio.h>intmain(){ charstr[4]="sv";//charstr[3]="sv";是错的 charstr[]="sv"; charstr[4]={'s','','v'......
  • 手撕快排的三种方法:让面试官对你刮目相看
    快来参与讨论......
  • 树分治
    点分治思想回想序列分治的做法:递归统计两个区间,在统计跨两个区间的贡献对应到树上也类似:找一个分割点,统计子树的贡献,再统计跨子树的贡献由于路径都可以被某一级重心统计到,所以点分治长于做路径统计问题找树的中心树的中心:以它为根时的最大子树最小的点处理方式:开全局变量\(......
  • DEV C++ 平台【openGL】库 几何变换下图案设计 星状图形 与 圆 的画法实现 【C语言】
     项目实现话不多说,上干货!    在本文中,我们将探讨如何使用OpenGL库在DEVC++平台上绘制一个包含星状图形和圆的设计。功能简单介绍    该代码通过定义多个函数,实现了圆和星状图形的精确绘制。首先,DrawingCircle函数负责绘制圆,通过指定圆心坐标和半径,利用三角......