【算法】快速选择算法

目录

  • 1.概述
  • 2.代码实现
    • 2.1.基于简单交换排序
    • 2.2.基于堆排序
    • 2.3.基于快速排序
  • 3.应用

更多数据结构与算法的相关知识可以查看数据结构与算法这一专栏。

1.概述

(1)快速选择算法 (Quick Select Algorithm) 是一种用于在无序数组中寻找第 k 小(或第 k 大)元素的高效算法,它是快速排序算法的一个变种。快速选择算法的基本思想是通过每次选取一个枢纽元素(通常是随机选择或者选择第一个或最后一个元素),将数组划分为两个部分,并将枢纽元素放置在其最终的排序位置上。然后,根据枢纽元素的位置,判断第 k 小(或第 k 大)元素在数组的左半部分还是右半部分,并且递归地在相应的部分中查找。

(2)与快速排序不同的是,快速选择算法只需要递归处理数组的一半。这样,在平均情况下,算法的时间复杂度为 O(n),其中 n 是数组的大小。然而,在最坏情况下,快速选择算法的时间复杂度可能达到 O(n2),但这种情况的概率很低。

2.代码实现

实现快速选择算法有许多方式,下面将逐一介绍。需要注意的是,在下面的实现方式中,只有基于快速排序的快速选择算法的时间复杂度为 O(n)。此外,有关排序算法的具体知识可以参考【算法】内部排序这篇文章。

注意:下面的代码实现都是找出数组中的第 k 个最大元素。

2.1.基于简单交换排序

这里考虑利用简单交换排序的特点,即每完成一趟排序,就至少会有一个元素的位置确定,那么只需要对数组 nums 进行 k 躺降序排序,此时的 nums[k - 1] 必为数组 nums 中第 k 个最大的元素,将其返回即可。该算法的时间复杂度为 O(k * n),具体实现代码如下:

class Solution {public int findKthLargest(int[] nums, int k) {//进行 k 趟排序for (int i = 0; i < k; i++) {for (int j = i + 1; j < nums.length; j++) {if (nums[i] < nums[j]) {//交换 nums[i] 和 nums[j]int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}}}return nums[k - 1];}
}

2.2.基于堆排序

我们可以使用堆排序来找出数组中的第 k 个最大元素,即建立一个大根堆,做 k − 1 次删除操作后堆顶元素就是我们要找的答案。在 Java 中,我们可以直接使用优先级队列来完成这一操作。当然,如果想更加了解推排序,我们也可以手动实现。该算法的时间复杂度为 O(nlogk),具体代码如下:

//使用优先级队列
class Solution {public int findKthLargest(int[] nums, int k) {//创建一个大根堆PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);//将数组中的元素加入堆for (int num : nums) {maxHeap.offer(num);}//进行 k - 1 次删除操作for (int i = 0; i < k - 1; i++) {maxHeap.poll();}//堆顶元素就是第 k 个最大元素return maxHeap.peek();}
}
//手动实现堆排序
class Solution {public int findKthLargest(int[] nums, int k) {int length = nums.length;//循环建立初始堆,调用 sift 算法 ⌊n / 2⌋ 次for (int i = (length - 1) / 2; i >= 0; i--) {sift(nums, i, length - 1);}for (int i = length - 1; i >= length - k; i--) {//将 nums[i] 与根 nums[0] 交换int tmp = nums[0];nums[0] = nums[i];nums[i] = tmp;//对 nums[0...i - 1] 进行筛选,得到 i 个节点的堆sift(nums, 0, i - 1);}return nums[length - k];}//对 nums[low...high] 进行筛选,使得以 nums[low] 为根节点的左子树和右子树均为大根堆public void sift(int[] nums, int low, int high) {// nums[j] 是 nums[i] 的左孩子int i = low;int j = (i == 0) ? 1 : 2 * i;int tmp = nums[i];while (j <= high) {//如果右孩子更大,则将 j 指向右孩子if (j < high && nums[j] < nums[j + 1]) {j++;}//根节点小于最大孩子节点if (tmp < nums[j]) {nums[i] = nums[j];nums[j] = tmp;i = j;j = 2 * i;} else {//如果跟节点大于等于最大孩子关键字,筛选结束break;}}}
}

2.3.基于快速排序

具体实现思路如下:

  • 首先定义一个函数 findKthLargest,该函数接收一个数组 nums 和一个整数 k,返回该数组中第 k 大的元素。
  • 在 findKthLargest 函数中,调用 quickSelect 函数进行快速选择,在数组 nums 的下标范围 [left, right] 中查找第 index 大的元素。
  • 在 quickSelect 函数中,首先调用 randomPartition 函数进行随机划分,得到基准元素的下标 q:
    • 判断 q 是否等于 index,如果相等则说明已经找到了第 k 大的元素,直接返回 nums[q]。
    • 如果 q 不等于 index,根据 q 在 [left, right] 中与 index 的大小关系,递归调用 quickSelect 函数查找第 k 大的元素。如果 q < index,则第 k 大的元素在右区间,递归调用 quickSelect(nums, q + 1, right, index);否则第 k 大的元素在左区间,递归调用 quickSelect(nums, left, q - 1, index)。
  • randomPartition 函数采用随机选择基准元素的方法,将一个下标 i 转换成基准元素,使得 [left,i-1] 中的所有元素都小于等于基准元素,[i+1, right] 中的所有元素都大于等于基准元素,并返回基准元素的下标。
  • partition 函数是快速排序中划分数组的核心操作,它以 nums[left] 为基准,将数组 nums[left…right] 划分为两个子数组 nums[left…i-1] 和 nums[i+1…right],并返回最终元素 nums[left] 所在的下标 i。此处实现的是双指针的快速排序实现方式。
class Solution {Random random = new Random();public int findKthLargest(int[] nums, int k) {//第 K 个最大元素其实就是升序排序后的元素 nums[nums.length - k]return quickSelect(nums, 0, nums.length - 1, nums.length - k);}public int quickSelect(int[] nums, int left, int right, int index) {int q = randomPartition(nums, left, right);if (q == index) {//当前划分的下标 q 等于 index,则说明这次划分操作后已经找到了第 k 个最大元素,其下标为 q,直接返回 nums[q] 即可return nums[q];} else {/*当前划分的下标 q 不等于 index: (1) 如果 q < index,那么则说明第 k 个最大元素在右区间,此时递归右区间(2) 如果 q > index,那么则说明第 k 个最大元素在左区间,此时递归左区间*/if (q < index) {return quickSelect(nums, q + 1, right, index)} else {return quickSelect(nums, left, q - 1, index);}}}public int randomPartition(int[] nums, int left, int right) {// random.nextInt(int num): 随机返回一个 [0, num) 内的整数int i = random.nextInt(right - left + 1) + left;int tmp = nums[i];nums[i] = nums[left];nums[left] = tmp;return partition(nums, left, right);}//以 nums[left] 为基准,进行一趟划分并返回最终元素 nums[left] 所在的下标private int partition(int[] nums, int left, int right) {int i = left, j = right;//以 nums[left] 为基准int benchmark = nums[left];while (i < j) {//从右向左扫描,找到一个小于 benchmark 的元素 nums[j]while (i < j && benchmark <= nums[j]) {j--;}nums[i] = nums[j];//从左向右扫描,找到一个大于 benchmark 的元素 nums[i]while (i < j && nums[i] <= benchmark) {i++;}nums[j] = nums[i];}nums[i] = benchmark;return i;}
}

3.应用

(1)快速选择算法在处理数组中第 k 大/小元素的问题上具有广泛的应用场景。以下是一些应用场景的示例:

  • 数组中的第 k 大/小元素:快速选择算法可以在线性时间复杂度内找到数组中的第 k 大/小元素。这在需要找到排名靠前/靠后的元素时非常有用,例如找到数组中的中位数或前 k 个最大的元素。
  • 排序问题:在快速排序算法中,使用快速选择算法找到基准元素的位置,然后通过递归地对基准元素左右两部分进行排序,最终实现对整个数组的排序。
  • 基于中位数的分割问题:在一些分割问题中,我们可以使用快速选择算法找到数组的中位数,并将数组分割成两个部分。这在一些分治算法中非常常见,例如快速排序、快速选择等。
  • 统计问题:快速选择算法可用于解决一些统计问题,例如找到数组中出现次数最多的元素、找到更大/更小的元素等。

总的来说,快速选择算法适用于需要在数组中查找第 k 大/小元素的问题,并且相比完全排序整个数组,快速选择算法具有更高的效率。它在平均情况下的时间复杂度为O(n),在最坏情况下的时间复杂度为O(n2),但通过一些优化技巧,可以将最坏情况下的时间复杂度降低到O(n)。

(2)例如,在 462.最小操作次数使数组元素相等 II 这题中,我们就可以使用快速选择算法来快速找出数组中的中位数,并计算使所有数组元素相等需要的最小操作数。

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

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

相关文章

鸿蒙开发已成新趋势

随着华为鸿蒙操作系统的快速崭露头角&#xff0c;鸿蒙开发已然成为当前技术领域的热门新趋势。本文将深入探讨鸿蒙开发的重要性和独特优势&#xff0c;并详细介绍一些关键的鸿蒙开发技术和工具&#xff0c;以及它们对开发者个人和整个行业带来的深远影响。 首先&#xff0c;鸿蒙…

入侵redis之准备---VMware安装部署kail镜像服务器【详细包含云盘镜像】

入侵redis之准备—VMware安装部署kail镜像服务器【详细包含云盘镜像】 kail是一个很好玩的操作系统&#xff0c;不多说了哈 下载kail镜像 kail官网:https://www.kali.org/get-kali/#kali-platforms 百度云盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1PRjo…

django框架ORM

1、安装mysqlclient pip install mysqlclient2、创建文件models.py from django.db import modelsclass UserInfo(models.Model):name models.CharField(max_length32)password models.CharField(max_length64)age models.IntegerField(3)3、再settings中注册app INSTALLE…

具身智能17篇创新性论文及代码合集,2023最新

今天来聊聊人工智能领域近期的一个热门研究方向——具身智能。 具身智能&#xff08;Embodied Intelligence&#xff09;指的是机器人或智能体通过感知、理解和交互来适应环境&#xff0c;并执行任务的能力。与传统的基于规则或符号的人工智能不同&#xff0c;具身智能强调将感…

基于springboot实现私人健身与教练预约管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现私人健身与教练预约管理系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应…

克服海外邮件收发难题,Coremail提供全方位解决方案

客户背景 某3C电子企业成立于2007年&#xff0c;总部位于深圳&#xff0c;公司在国内多个重点城市均设有研发和生产基地。作为一家规模庞大、技术领先的国家高新技术企业&#xff0c;该企业始终致力于锂离子电池的研究与发展&#xff0c;为新能源行业提供专业、系统的材料解决…

hello vtk 圆柱

VTK 可视化的流程及步骤 标题引入VTK库和初始化&#xff1a; 引入 VTK 库和 AutoInit 模块&#xff0c;以便使用 VTK 的渲染和交互功能 设置背景颜色和颜色对象&#xff1a; 使用 vtkNamedColors 设置背景颜色和演员颜色。 创建圆柱体源&#xff1a; 使用 vtkCylinderSou…

蓝桥杯-01简介

文章目录 蓝桥杯简介参考资源蓝桥杯官网第15届大赛章程一、概况&#xff08;一&#xff09;大赛背景和宗旨&#xff08;二&#xff09;大赛特色&#xff08;三&#xff09;大赛项目1.Java软件开发2.C/C程序设计3.Python程序设计4.Web应用开发5.软件测试6.网络安全7.嵌入式设计与…

可视化文件编辑与SSH传输神器WinSCP如何公网远程本地服务器

可视化文件编辑与SSH传输神器WinSCP如何公网远程本地服务器 文章目录 可视化文件编辑与SSH传输神器WinSCP如何公网远程本地服务器1. 简介2. 软件下载安装&#xff1a;3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 …

CH02_交给子类

Template Method模式 组成模板的方法被定义在父类中&#xff0c;由于这些方法是抽象方法&#xff0c;所以只查看父类的代码是无法知道这些方法最终会进行何种具体处理的。唯一能知道的就是父类如何调用这些方法。 类图 说明 AbstractClass&#xff08;抽象类&#xff09; Abs…

vue项目中通过vuex管理数据

目录 1.前言&#xff1a; 2.vuex的基础用法&#xff1a; 1.构建与挂载vue 基础模板渲染 构建仓库 2.mutations的使用 1.介绍 ​编辑 2.案列&#xff1a; 3.传参 4.辅助函数mapMutations&#xff1a; 3.module分对象的写法 介绍 建立模块&#xff1a; 访问数据的方…

【VROC】看Intel VROC如何给NVMe SSD做RAID

在当今对硬盘性能要求越来越高的环境中&#xff0c;SATA和SAS接口由于自身的限制&#xff0c;其性能很难突破600MiB/s的瓶颈。因此&#xff0c;对于需要更高底层硬件性能的行业&#xff0c;如数据库等&#xff0c;对NVMe盘的需求越来越迫切。然而&#xff0c;NVMe盘直通到CPU&a…

三种常见的哈希结构

1.数组 2.set 使用序引用set头文件 unordered_set需引用unordered_set 3.map unordered_map需引用unordered_map头文件

error: ‘PixelPacket’ in namespace ‘Magick’ does not name a type

最近做一个项目需要配置ImageMagick库&#xff0c;本项目配置环境如下&#xff1a; ImageMagick version 7 Operating system, version and so on ubuntu 20.04 Descriptionerror: ‘PixelPacket’ in namespace ‘Magick’ does not name a type 这是在运行程序时候出现的问题…

[RK-Linux] recovery分区详解(二)

承接上一篇:[RK-Linux] recovery分区详解(一) 文章目录 四、SD 卡启动盘升级过程分析4.1 制作 SD 固件升级卡4.2 u-boot 执行4.3 recovery 执行五、恢复出厂设置四、SD 卡启动盘升级过程分析 4.1 制作 SD 固件升级卡 准备好 PC 机(windows 系统)、SD 卡、USB 读卡器。 SD…

优维低代码实践:搜索功能

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

设单链表中有仅三类字符的数据元素(大写字母、数字和其它字符),要求利用原单链表中结点空间设计出三个单链表的算法,使每个单链表只包含同类字符。

使用C语言编写的算法,将原单链表根据字符类型拆分为三个单链表。其中,大写字母链表(upperList)、数字链表(digitList)和其他字符链表(otherList)分别用于存储相应类型的字符。 `Upper Case List`存储了大写字母A、C, `Digit List`存储了数字1、2、3, `Other List`存…

C语言数据结构-----栈和队列练习题(分析+代码)

前言 前面的博客写了如何实现栈和队列&#xff0c;下来我们来看一下队列和栈的相关习题。 链接: 栈和队列的实现 文章目录 前言1.用栈实现括号匹配2.用队列实现栈3.用栈实现队列4.设计循环队列 1.用栈实现括号匹配 此题最重要的就是数量匹配和顺序匹配。 用栈可以完美的做到…

Egg.js中Cookie和Session

Cookie HTTP请求是无状态的&#xff0c;但是在开发时&#xff0c;有些情况是需要知道请求的人是谁的。为了解决这个问题&#xff0c;HTTP协议设计了一个特殊的请求头&#xff1a;Cookie。服务端可以通过响应头&#xff08;set-cookie&#xff09;将少量数据响应给客户端&#…

已解决java.lang.exceptionininitializererror异常的正确解决方法,亲测有效!!!

已解决java.lang.exceptionininitializererror异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录 报错问题解决思路解决方法交流 报错问题 java.lang.exceptionininitializererror 解决思路 java.lang.ExceptionInInitializerError 是一…