【分治】--- 快速选择算法

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     算法Journey  


🏠 颜色划分

📌 题目解析

颜色分类

  • 本题要求我们原地对元数组划分0,1,2三个区域,也就是不能使用辅助数组,同时不能使用库中内置的sort函数。

📌 算法原理

【双指针算法】--- 移动零 && 复写零_移动零 双指针-CSDN博客

在做这道题中,我们可以复习一下移动零的思路。在这道题中也是要求我们对数组进行划分,只不过划分为非0和0两个区域。移动零这道题中可以使用双指针dest和cur来界定非0和0两个区域,当进行遍历数组的指针cur遇到非0的数时,此时需要移动dest将这个非0的数并入[0,dest]区间;当遇到0时,此时0就是在[dest,cur]的区域内,只需让cur继续移动即可;最后直到cur遍历完数组

类比移动零的思路,我们可以先把数组划分为全是2和非2的区域,然后再把非2的区域划分为非1和1的区域

class Solution {
public:// 思路:三指针// 首先将整个数组划分为两块void sortColors(vector<int>& nums) {int cur = 0;int dest = -1;// 第一次划分左边是0,1 右边是2while (cur < nums.size()) {if (nums[cur] == 2)cur++;else {dest++;swap(nums[dest], nums[cur]);cur++;}}// 划分左边区域cur = 0;dest = -1;while (cur < nums.size()) {if (nums[cur] == 2)break;if (nums[cur] == 1)cur++;else if(nums[cur] == 0){dest++;swap(nums[dest], nums[cur]);cur++;}}}
};

我们能否让cur指针遍历完数组的同时,一次性把三个区域划分好呢?

我们可以规定:[0,left]属于全都是0的区域,[left+1,i-1]属于全是1的区域,[right,n-1]属于全都是2

的区域,而同样的我们需要一个遍历数组的指针i,因此[i,right]属于待扫描的区域。

Q:为什么i遇到2交换完后i不能向前移动,而遇到1时可以?

--right之后,right所处的位置是待扫描的,此时交换过来之后不知道是0/1/2,仍然需要处理,需要重新判断;而++left之后,由于left所处的位置是i扫描过的,所以可以放心交换。

Q:i遍历到何时结束?

当i遍历到right时说明三个区域 已经划分完了,没有继续往后走的必要了。

参考代码:

class Solution {
public:void sortColors(vector<int>& nums){int n = nums.size();int left = -1;int i = 0;int right = n;while (i != right){if (nums[i] == 0) swap(nums[++left], nums[i++]);else if (nums[i] == 1)i++;elseswap(nums[--right], nums[i]);}}
};

🏠 排序数组

📌 题目解析

排序数组

  • 本题也不允许使用内置函数比如sort()。

📌 算法原理

本题我们采用快速排序求解下。

先回忆一下简单的一个快速排序的基本思想:

  • 先确定一个基准key。
  • right移动时寻找比key小的,left移动时寻找比key大的。
  • swap(nums[right],nums[left]);
  • 数组被划分为左边是小于key,右边是大于key。
  • 左边部分也按照上述逻辑处理,右边部分也这样处理,直到无法再划分区间。

快速排序整体思想中主要也是对数组进行区域划分,将左边划分为小于等于key,右边划分大于key,但是本道题单纯这样写一个快排是会超时的,当数组都是重复的元素时,此时快排会退化为O(N^2)。如果我们使用前面的"数组分三块"的三路划分思想,此时处理完整个数组,处理左右两边时就不需要处理重复元素了,因为重复元素都被划分进中间区域了

总结步骤:

优化 :  在常规快排中,我们常选取左端的第一个数作为key,当数组接近有序时,此时会退化为O(N^2),此时我们可以采取三数取中/随机数尽可能避免这种极端情况。同时我们选择随机数时可以选择让rand()%(right-left+1)使取值范围为[left,right],再加上left之后就能映射到我们选的区间。

参考代码:

class Solution {
public:void QuickSort(vector<int>& nums, int left, int right){if (left >= right)return;int randi = rand()%(right-left+1);randi += left; //随机数int key = nums[randi];int i = left;int begin =  left  - 1;int end   =  right + 1 ;while (i != end){ if(nums[i] < key)//< key区域swap(nums[++begin],nums[i++]);else if(nums[i] == key)  // ==key区域i++;else // > key区域swap(nums[--end],nums[i]);}//排左边 右边QuickSort(nums, left, begin);QuickSort(nums, end, right);}vector<int> sortArray(vector<int>& nums){srand(time(NULL));QuickSort(nums, 0, nums.size() - 1);return nums;}
};

🏠 数组中的第k大个元素

📌 题目解析

数组中的第K个最大元素

  • 本题需要设计时间复杂度为O(N)的算法。

📌 算法原理

  • 思路1:排序 + 计数器
class Solution {
public:int findKthLargest(vector<int>& nums, int k){ sort(nums.begin(),nums.end());int cur = nums.size() - 1;int count = 0;while(count < k){count++;if(count < k)cur--;}// 1 2 3 4 5 6  k = 2return nums[cur];}
};
  • 思路2 :堆排序(Top K)
class Solution {
public:
//建大堆int findKthLargest(vector<int>& nums, int k){ priority_queue<int> pq(nums.begin(),nums.end());while(k>1){pq.pop();k--;}return pq.top();}
};
  • 思路3 :桶排序

   前面两种思路虽然能解决问题,但严格讲并不是O(N)的时间复杂度,而桶排序就完美的符合,但是需要空间换时间,我们可以根据题目给定的数据范围开好数组进行映射。

class Solution {
public:
//桶排序 相对映射 int findKthLargest(vector<int>& nums, int k){ int arr [20001] = {0};//遍历nfor(auto e : nums){arr[e+10000]++;}int num = 0;//遍历20001for(int i = 20000;i>=0;i--){k = k - arr[i];if(k <= 0){num = i-10000;break;}}return num;}
};
  • 思路4:快速选择算法

基于快速选择算法,我们可以快速划分出三个区域,基于三个区域各自元素个数定位出第k大元素的区间。

参考代码:

class Solution {
public:int QuickSort(vector<int>& nums,int left,int right,int k){if(left == right)return nums[left];int key = nums[left];int begin = left-1;int end = right+1;int i = left;while(i < end){if(nums[i] < key) swap(nums[++begin],nums[i++]);else if(nums[i] == key) i++;else swap(nums[--end],nums[i]);  } // [left,begin] (begin,end) [end,right]int midNum = end - begin - 1;int RightNum = right - end + 1;if (LeftNum >= k) return QuickSort(nums, left, begin, k);else if (midNum + LeftNum >= k) return key;elsereturn QuickSort(nums, end, right, k - midNum - LeftNum);}int findKthLargest(vector<int>& nums, int k){return QuickSort(nums,0,nums.size()-1,k);          }
};

注:考虑到退化的情况我们可以自行采取随机数/三路取中的优化方法。

快速选择算法也常常用于解决TopK问题,和快排不同的是,快速选择并不对左右两部分子数组都进行递归,而只对寻找的目标所在的子数组进行递归。也正因如此,快速选择算法将平均时间复杂度从O(nlogn)降到O(n),而最坏情况下我们不能保证每次选取的key是中间值,此时时间复杂度会退化为O(n^2),为了尽可能防止极端情况发生,我们需要在算法开始的时候对nums数组来一次随机打乱。

关于快速选择算法的时间复杂度计算可参考这篇文章:快速选择

  • 思路5:库函数

nth_element函数第一个参数是起始位置,第二个参数是要查找元素的位置,第三个参数是最后一个元素位置+1;nth_element(a,a+k,a+n)意思就是把数组中第k小(默认是第k小)的数放在k下标,而对其他元素没有排序,但是k左边都是比它小的,k右边都是比它大的。如果你想求第k大,可以传第四个参数也就是仿函数,也可以将求第k大转化为求第n-k+1小,对应就是array[n-k];

class Solution
{
public:int findKthLargest(vector<int>& a, int k) {int n = a.size();nth_element(a.begin(),a.begin()+n-k,a.end());return a[n-k];}
};

🏠 最小的k个数

📌 题目解析

最小的k个数

  • 本题要返回的不是第k小的数,而是要返回前k小的所有数,顺序不限。

📌 算法原理

  • 思路1:排序(NlogN)
  • 思路2:堆(Nlogk)
  • 思路3:快速选择算法 O(N)

当快速选择完之后就能把数组划分为三个区域,左边是比第k小还要小的数,中间是第k小,右边是比第k小的数还要大的数,我们直接返回左边和中间即可。

参考代码:

vector<int> getLeastNumbers(vector<int>&nums, int k)
{ srand(time(NULL));qsort(nums,0,nums.size()-1,k);return{nums.begin(),nums.begin()+k
};
void qsort(vector<int>&nums,int l,int 1, int r, int k)
{if(l>=r)return;
//1.随机选择一个基准元素+数组分三块  int key = getRandom(nums,1,r);int left=1,right=right=r +1, i = 1, i = 1;while(i<right){if(nums[i]<key) swap(nums[++left],nums[i++]);else if(nums[i]== key) i++;else swap(nums[--right],nums[i]);}// [1,left][left + 1, right - 1][right,r]//2.分情况讨论int a = left-1+1,b=right-left-1;if(a>k) qsort(nums,1,left,k);else if(a+b>=k)return;else qsort(nums,right,r,k-a - a - b);
}
int getRandom(vector<int>&nums,intl,int l, int r)
{return nums[rand() %(r-1+1)+1)+1) + 1];
}

总结: 本篇博客我们介绍了快速排序的衍生算法快速选择算法,本算法也是基于分治的思想进行快速定位区间,其可以使某些需要 O(nlogn)时间复杂度的问题,在平均复杂度O(n)下完成。常见的例子是求数组的第 k 小的数,Top k问题等,与快排不同的是快速选择只对寻找的的目标所在的子数组进行递归。同时我们介绍了当数组全是重复元素时对快排的优化方案:三路划分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/478480.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

万物皆可Docker,在NAS上一键部署最新苹果MacOS 15系统

万物皆可Docker&#xff0c;在NAS上一键部署最新苹果MacOS 15系统 哈喽小伙伴们还&#xff0c;我是Stark-C~ 最近苹果Mac mini 2024款在政府补贴的加持下&#xff0c;仅需3500块钱左右就能到手确实挺香的。我看很多评论区的小伙伴跃跃欲试&#xff0c;但是也有不少之前从未体…

C++设计模式行为模式———状态模式

文章目录 一、引言二、状态模式三、总结三、总结 一、引言 状态模式是一种行为设计模式&#xff0c; 让你能在一个对象的内部状态变化时改变其行为&#xff0c; 使其看上去就像改变了自身所属的类一样。其实现可完成类似有限状态机的功能。换句话说&#xff0c;一个对象可以处…

vscode自动打印日志插件

自动日志工具&#xff08;Auto Logger Log&#xff09; 概述 自动日志工具&#xff08;Auto Logger Log&#xff09; 是一款 VS Code 扩展&#xff0c;用于简化生成调试日志的过程。它可以为选中的变量自动生成打印语句&#xff0c;帮助开发者快速记录和调试代码。该扩展支持多…

优雅的不等式——Hard

上一文《Easy》末尾出现了又要我们证明的例子&#xff0c;Hard难度就是继续答题答下去 其实一样可以用那篇文章https://zhuanlan.zhihu.com/p/669285539中的式子继续算下去&#xff0c;但是有三个系数&#xff0c;实在是太费时间和人力了 翻到下面的第十九种类型&#xff0c;可…

虚拟局域网PPTP配置与验证(二)

虚拟局域网PPTP配置与验证(二) windows VPN客户端linux 客户端openwrt客户端性能验证虚拟局域网PPTP配置与验证(一)虚拟局域网PPTP配置与验证(二) : 本文介绍几种客户端连接PPTP服务端的方法,同时对linux/windows/openwrt 操作系统及x86、arm硬件平台下PPTP包转发性能进…

Move 合约部署踩坑笔记:如何解决 Sui 客户端发布错误Committing lock file

Move 共学活动&#xff1a;快速上手 Move 开发 为了帮助更多开发者快速了解和掌握 Move 编程语言&#xff0c;Move 共学活动由 HOH 社区、HackQuest、OpenBuild、KeyMap 联合发起。该活动旨在为新手小白提供一个良好的学习平台&#xff0c;带领大家一步步熟悉 Move 语言&#…

介绍一下strupr(arr);(c基础)

hi , I am 36 适合对象c语言初学者 strupr(arr)&#xff1b;函数是把arr数组变为大写字母 格式 #include<string.h> strupr(arr); 返回值为arr 链接分享一下arr的意义(c基础)(必看)(牢记)-CSDN博客 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #incl…

进程间通信5:信号

引入 我们之前学习了信号量&#xff0c;信号量和信号可不是一个东西&#xff0c;不能混淆。 信号是什么以及一些基础概念 信号是一种让进程给其他进程发送异步消息的方式 信号是随时产生的&#xff0c;无法预测信号可以临时保存下来&#xff0c;之后再处理信号是异步发送的…

浅谈网络 | 传输层之套接字Socket

目录 基于 TCP 协议的 Socket 程序调用过程基于 UDP 协议的 Socket 程序函数调用过程服务器如何接入更多的项目构建高并发服务端&#xff1a;从多进程到 IO 多路复用 在前面&#xff0c;我们已经介绍了 TCP 和 UDP 协议&#xff0c;但还没有实践过。接下来这一节&#xff0c;我…

Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图

用户打开 PDF 文档时&#xff0c;他们会看到 PDF 的初始视图。默认情况下&#xff0c;打开 PDF 时不会显示书签面板或缩略图面板。在本文中&#xff0c;我们将演示如何设置文档属性&#xff0c;以便每次启动文件时都会打开书签面板或缩略图面板。 Spire.PDF for .NET 是一款独…

FileLink内外网文件共享系统与FTP对比:高效、安全的文件传输新选择

随着信息技术的不断进步&#xff0c;文件传输和共享已经成为企业日常工作中不可或缺的一部分。传统的FTP&#xff08;File Transfer Protocol&#xff09;协议在一定程度上为文件共享提供了便利&#xff0c;但随着企业对文件传输的需求越来越复杂&#xff0c;FileLink内外网文件…

神经网络归一化方法总结

在深度学习中&#xff0c;归一化 是提高训练效率和稳定性的关键技术。以下是几种常见的神经网络归一化方法的总结&#xff0c;包括其核心思想、适用场景及优缺点。 四种归一化 特性Batch NormalizationGroup NormalizationLayer NormalizationInstance Normalization计算维度…

ORB-SLAM2源码学习:Initializer.cc:Initializer::ComputeF21地图初始化——计算基础矩阵

前言 在平面场景我们通过求解单应矩阵H来求解位姿&#xff0c;但是我们在实际中常见的都是非平面场景&#xff0c; 此时需要用基础矩阵F求解位姿。 1.函数声明 cv::Mat Initializer::ComputeF21(const vector<cv::Point2f> &vP1, const vector<cv::Point2f>…

离散化 C++

题目 解题思路 将所有对坐标的访问用map映射到一个新的坐标轴上再在新的坐标轴上进行加法用前缀和快速求出区间的和 代码实现 #include<iostream> #include<algorithm> #include<unordered_map>using namespace std;typedef pair<int, int> PII;con…

uniop触摸屏维修eTOP40系列ETOP40-0050

在现代化的工业与商业环境中&#xff0c;触摸屏设备已成为不可或缺的人机交互界面。UNIOP&#xff0c;作为一个知名的触摸屏品牌&#xff0c;以其高性能、稳定性和用户友好性&#xff0c;广泛应用于各种自动化控制系统、自助服务终端以及高端展示系统中。然而&#xff0c;即便如…

机器学习与图像处理中上采样与下采样

一、机器学习中的上采样 目的&#xff1a;在机器学习中&#xff0c;上采样用于处理不平衡数据集&#xff0c;即某些类别的样本数量远多于其他类别。上采样的目标是通过增加少数类样本的数量来平衡类别分布&#xff0c;从而提高模型对少数类的识别能力。 1.随机过采样&#xff0…

Unity中动态生成贴图并保存成png图片实现

实现原理&#xff1a; 要生成长x宽y的贴图&#xff0c;就是生成x*y个像素填充到贴图中&#xff0c;如下图&#xff1a; 如果要改变局部颜色&#xff0c;就是从x1到x2(x1<x2),y1到y2(y1<y2)这个范围做处理&#xff0c; 或者要想做圆形就是计算距某个点&#xff08;x1,y1&…

DHCP服务(包含配置过程)

目录 一、 DHCP的定义 二、 使用DHCP的好处 三、 DHCP的分配方式 四、 DHCP的租约过程 1. 客户机请求IP 2. 服务器响应 3. 客户机选择IP 4. 服务器确定租约 5. 重新登录 6. 更新租约 五、 DHCP服务配置过程 一、 DHCP的定义 DHCP&#xff08;Dynamic Host Configur…

认识RabbitMq和RabbitMq的使用

1 认识RabbitMq RabbitMQ是⼀个消息中间件&#xff0c;也是⼀个生产者消费者模型&#xff0c;它负责接收&#xff0c;存储并转发消息。 2.1 Producer和Consumer Producer&#xff1a;生产者&#xff0c;是RabbitMQServer的客户端&#xff0c;向RabbitMQ发送消息 Consumer&…

HTML飞舞的爱心

目录 系列文章 写在前面 完整代码 代码分析 写在后面 系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色…