快速排序 O(nlgn)

news2024/11/26 22:40:37

大家好,我是蓝胖子,我一直相信编程是一门实践性的技术,其中算法也不例外,初学者可能往往对它可望而不可及,觉得很难,学了又忘,忘其实是由于没有真正搞懂算法的应用场景,所以我准备出一个系列,囊括我们在日常开发中常用的算法,并结合实际的应用场景,真正的感受算法的魅力。

代码已经上传github

https://github.com/HobbyBear/codelearning/tree/master/quicksort

算法原理

快速排序的时间复杂度是O(nlgn) ,这类时间复杂度的算法逻辑(归并排序也是类似)在算法逻辑上都有个特点,一般是将整个数组一分为二,然后将分割后的数组继续一分为二,这样整个切割过程就被分成了lg n 层,然后每一层需要对数组中的元素进行操作(这个操作针对于快速排序而言就是找到基点的位置,针对于归并排序而言就是合并相邻两部分的数组),由于每个都要对这个n个元素进行操作,所以整体的时间复杂度算下来就是O(nlgn) 。

接下来我们来仔细看下快速排序的算法逻辑,看看它是如何将数组进行切分的

下面👇🏻介绍的是三路快排的原理,相信懂了三路快排,双路快排也是很好理解的。

Pasted image 20230905103754.png

图中l代表数组的左边界,r代表数组的右边界,i代表当前遍历的元素

算法首先是在数组中选取数组首个元素作为基点,也就是图中的V,然后在遍历数组的过程中,划分出了3个空间,其中[l+1…lt] 部分会存放小于基点的元素, [gt…r] 不分在遍历完成后会存放大于基点的元素。[lt+1…gt-1]这部分区域在遍历完成后会存放等于基点的元素。在遍历完成的最后一步,则是将基点v和lt指向的元素交换位置,同时lt进行减一操作,这样最后完成遍历后,[l…lt]部分则全部放置的是小于基点的元素了。

你可以看到,在遍历完整个数组后,基点就已经找到了它在数组中应该存放的位置了,接下来就是对小于基点的部分数组,和大于基点的部分数组再次执行这个选基点分割的过程,这样每个元素便都能找到各自在数组中的正确位置。

知道了算法的大致逻辑,我们来对遍历元素过程中的三种情况进行分析,因为遍历过程中无非就是3种情况,大于V,小于V,等于V,我们应该如何移动对应的指针来让遍历完数组后,各个指针对应的区间仍然满足上面的定义就是我们需要探讨的。

小于V

Pasted image 20230905105625.png

假设当前遍历到了元素e,发现 e< V ,那么就需要将e和lt+1指向的元素交换位置(lt+1位置放置的是等于V的元素),并且更新lt指针位置到lt+1,同时需要将i进行加1操作继续遍历下一个元素。

等于V

Pasted image 20230905110303.png

假设当前遍历到的元素是e,发现e == V ,那么只需要执行i++ ,让i继续遍历下一个元素即可。

大于V

Pasted image 20230905110402.png
假设当前遍历的元素e > V ,那么需要将e通gt-1位置的元素进行交换,由于交换过来的gt-1位置的元素还未遍历,所以在这一步,i不进行加1操作,gt–即可。

实现

在详细的看完快排算法的思路之后,我们用代码来实现下这部分。

很多时候写算法不能写出来,是因为你不能完整的用文字清晰的描述算法的逻辑,当你能清晰的描绘整个算法逻辑之后,写代码实现起来便是水到渠成的事。

首先,我们定义一个快排的函数,它的定义是对arr数组中的[l…r]区间内的元素进行快排。

func quickSort(arr []int, l int, r int) 

在函数内部,我们需要基点,和几个指针,lt,gt,i 它们分别对应上图中的小于V的边界,大于V的边界,当前要遍历的元素,注意我们定义的lt,gt边界都是闭区间。

在初始化边界时,大于V和小于V 的区间都是没有元素的。

lt := l     // 小于v的右边界  
gt := r + 1 // 大于v的左边界  
i := l + 1  // 当前遍历的元素
v := arr[l]  //  V 为选定的基点

剩下的就是按刚才探讨的遍历i过程对数组进行遍历,遍历的介绍条件则是i < gt 时停止遍历,完整代码如下:

func quickSort(arr []int, l int, r int) {  
   if l >= r {  
      return  
   }  
   lt := l     // 小于v的右边界  
   gt := r + 1 // 大于v的左边界  
   i := l + 1  // 当前遍历的元素  
   swap(arr, l, rand.Int()%(r-l+1)+l)  //  这一步是为了让基点随机化,否则快排在排序近乎有序数组时,不能很好达到切分数组的目的,从而让算法退化成O(n^2)的算法
   v := arr[l]  
   for i < gt {  
      if v > arr[i] {  
         tmp := arr[lt+1]  
         arr[lt+1] = arr[i]  
         arr[i] = tmp  
         lt++  
         i++  
         continue  
      }  
      if v < arr[i] {  
         gt--  
         tmp := arr[gt]  
         arr[gt] = arr[i]  
         arr[i] = tmp  
         continue  
      }  
      i++  
   }  
   tmp := arr[lt]  
   arr[lt] = v  
   arr[l] = tmp  
   lt--  
   quickSort(arr, l, lt)  
   quickSort(arr, gt, r)  
}

(应用场景)在O(n)时间复杂度内选取数组中第k大的元素

接下来,我们来看一个利用快排选取基点切分数组的思想来解决选取数组中第k大元素的问题。这是leetcode的 215号题。

215. 数组中的第K个最大元素  
中等  
2.3K  
相关企业  
给定整数数组 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  
  
提示:  
  
1 <= k <= nums.length <= 105  
-104 <= nums[i] <= 104

看完题目后,我们来回顾下快排在遍历一次数组后达到了什么效果,是找到了基点在数组中的正确位置。如果这个位置正是数组中第k大的元素,那么不就找到了这个位置吗。基于此,我们来探讨下,最后基点的位置和k的关系,注意如果数值是从大到小进行排序,那么第k大的元素索引是k-1

和前面讲解三路快排不同,最后遍历完成后 在[l…lt]部分存放大于V的元素,在[gt…r]部分存放小于V的元素。因为我们需要从大到小排列元素。接着探讨3中情况,

基点最后的索引位置等于k-1

即 lt + 1 == k -1 ,此时我们找到了第k大的元素,可以直接返回。

基点最后索引位置 > k-1

即lt+ 1 > k -1 ,说明第k大的元素在基点的左侧,即要在[l…lt]区间继续寻找

基点最后索引位置 < k - 1

即lt + 1 < k - 1 ,说明第k的元素在基点的右侧,需要往[lt+2…r]区间进行寻找,这里还有个优化点,由于[lt+1…gt-1] 之间是存放的等于V的部分,所以如果k-1 是在[lt+1…gt-1]范围的话,还是可以直接返回基点的值,如果k-1 >= gt 的话 说明第k大的元素在 [gt…r]区间,则应该在[gt…r]区间继续寻找元素。

完整代码如下:

func findKthLargest(nums []int, k int) int {  
   return partition(nums, k, 0, len(nums)-1)  
}  
  
// 大于 v     小于v  
// v [l+1..lt]     [gt...r] 进行分区,判断 分区后的lt+1 和k的大小  
func partition(nums []int, k int, l, r int) int {  
   lt := l  
   v := nums[l]  
   gt := r + 1  
   i := l + 1 // 当前遍历元素  
   for i < gt {  
      if nums[i] < v {  
         gt--  
         swap(nums, gt, i)  
         continue  
      }  
      if nums[i] > v {  
         lt++  
         swap(nums, i, lt)  
         i++  
         continue  
      }  
      i++  
   }  
   swap(nums, l, lt)  
   lt--  
   // lt+ 1的元素处于正确位置lt+1  
   if lt+1 == k-1 {  
      return nums[lt+1]  
   }  
   if lt+1 < k-1 {  
      if gt > k-1 {  
         return nums[k-1]  
      }  
      return partition(nums, k, gt, r)  
   }  
   return partition(nums, k, l, lt)  
}

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

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

相关文章

el-dialog两个弹框里面套弹框受外层弹框影响

el-dialog嵌套的影响及解决方法 解决方法如下&#xff1a; 在里层弹框里添加 append-to-body <el-dialogtitle"图片预览":visible.sync"dialogVisible"class"imgDialog":modal"false"append-to-body><img width"100%&q…

分享一个查询OpenAI Chatgpt key余额查询的工具网站

OpenAI Key 余额查询工具 欢迎使用 OpenAI Key 余额查询工具网站&#xff01;这个工具可以帮助您轻松地验证您的 OpenAI API 密钥&#xff0c;并查看您的余额。 http://tools.lbbit.top/check_key/ 什么是 OpenAI Key 余额查询工具&#xff1f; OpenAI Key 余额查询工具是一…

要想成为黑客,离不开这些资料

目录 一、想入门学黑客&#xff0c;去哪里找详细的教程&#xff1f; 二、适合新人入门的书籍 三、相关网站推荐 四、在线靶场 五、Web安全学习路线 六、Web安全入门基础学习 小白在学习黑客的过程中一般会遇到这样一些问题&#xff1a;感觉自己工具、原理都会了但是遇到真…

ue5打包失败与优化项目

打包报错&#xff1a; PackagingResults: Error: Multiple game targets found for project. Specify the desired target using the -Target... argument. 解决方案&#xff1a; 关闭项目后&#xff0c;删除项目目录下的 Intermediate 文件 再重新启动项目打包即可 参考&…

小学英语教学计划模板范文 英语优秀教案模板

小学英语课教学计划模板&#xff1a; 课程时长&#xff1a;40分钟/节 课程目标&#xff1a;本课程的目标是让学生达到一定的英语水平&#xff0c;包括词汇、语法、听说读写能力等。 授课内容&#xff1a; 主题 1&#xff1a;Unit 1 Greetings 内容&#xff1a;学习如何用英…

[Python小项目] 从桌面壁纸到AI绘画

从桌面壁纸到AI绘画 一、前言 1.1 确认问题 由于生活和工作需要&#xff0c;小编要长时间的使用电脑&#xff0c;小编又懒&#xff0c;一个主题用半年的那种&#xff0c;所以桌面壁纸也是处于常年不更换的状态。即时改变主题也是在微软自带的壁纸中选择&#xff0c;而这些自…

机器学习-有监督学习-神经网络

目录 线性模型分类与回归感知机模型激活函数维度诅咒过拟合和欠拟合正则数据增强数值稳定性神经网络大家族CNNRNNGNN&#xff08;图神经网络&#xff09;GAN 线性模型 向量版本 y ⟨ w , x ⟩ b y \langle w, x \rangle b y⟨w,x⟩b 分类与回归 懂得两者区别激活函数&a…

项目成本超支的主要原因以及解决方法

成本超支&#xff0c;是每个项目经理在其职业生涯中都会遇到的一个问题。当项目的实际成本超过估计或预算成本时&#xff0c;就会发生成本超支。这在建筑、制造和软件开发项目中尤其常见&#xff0c;并影响着项目的盈利能力、利益相关者满意度和竞争优势。 成本超支的原因 由…

LINUX定时解压缩方案

需求背景 对接客户中某个上游为外包系统&#xff0c;外包系统每日推送压缩文件至指定文件夹下&#xff0c;文件格式为YYYYMMDD_RegReport.zip。由于每日采集文件&#xff0c;无法对接压缩包内文件&#xff0c;需要将推送的压缩文件每日解压为文件夹 需求分析 与客户沟通后&a…

苹果电脑其他内存怎么清理?

苹果电脑中的应用程序大部分是可以通过将其拖拽至废纸篓并倾倒来卸载的。但是部分程序在卸载后仍有残留文件&#xff0c;比如support文件和pref设置等文件的。小编今天介绍下苹果电脑清理内存怎么清理卸载残留以及好用的清理技巧分享。 一、苹果电脑清理内存怎么清理 苹果电脑…

分享38个AI绘画网站

本文是参考AI沉思录「1000AI」栏目的第十二期&#xff0c;「1000AI」栏目专注研究有哪些AI产品&#xff0c;目标研究1000AI产品(进度:532/1000)。 AI沉思录 ​aichensilu.com/ 1、Midjourney 网址&#xff1a;https://www.midjourney.com/ 基于diffusion的AI艺术生成器。生成…

vue3 vue.config.js分包配置

主要用到的是 filename 和 chunkFilename 两个方法 方法一&#xff1a;configureWebpack.output配置 代码&#xff1a; module.exports { configureWebpack: {devtool: source-map,output: {filename: js/dong/[name].[chunkhash:8].js,chunkFilename: js/xxxd/[name].[chu…

通过商品ID获取淘宝天猫商品评论数据,淘宝商品评论接口,淘宝商品评论api接口

淘宝商品评论内容数据接口可以通过以下步骤获取&#xff1a; 登录淘宝开放平台&#xff0c;进入API管理控制台。在API管理控制台中创建一个应用&#xff0c;获取到应用的App Key和Secret Key。构造请求URL&#xff0c;请求URL由App Key和Secret Key拼接而成&#xff0c;请求UR…

1.安装环境

学习Java的第一步应该从配置环境开始&#xff0c;这篇博文介绍了在哪下载安装包以及如何在windows电脑中配置环境&#xff0c;希望大家看完后可以独立安装 ~ 文章目录 一、下载安装包二、 配置环境 一、下载安装包 安装包可以从官网下载&#xff0c;也可以直接私信我拿取。这里…

软设上午题错题知识点2

软设上午题-错题知识点2 1、模块独立性是创建良好设计的一个重要原则&#xff0c;一般采用模块间的耦合和模块的内聚两个准则来进行度量。 内聚是指模块内部各元素之间联系的紧密程度&#xff0c;内聚度越高&#xff0c;则模块的独立性越好。 内聚性一般有以下几种&#xff1a…

记录一次通过openVPN访问域名无法访问的问题

OpenVPN访问域名失败 1.问题描述&#xff1a;2.分析3.解决 1.问题描述&#xff1a; 电脑需要通过openvpn访问一个域名&#xff0c;结果浏览器访问失败&#xff0c;ping域名直接超时了 浏览器访问截图&#xff1a; ping 域名截图 2.分析 1.因为要通过vpn访问所以肯定是对ip…

工业互联网系列2 - 赋能传统制造业

工业互联网被称为“第四次工业革命”&#xff0c;它将计算、信息与通讯网络相融合&#xff0c;应用于传统的制造业带来制造业的全面升级&#xff0c;实现了生产效率的提高、质量的改进、成本的降低和生产流程的优化。 汽车制造已经达到非常高的智能化和自动化水平&#xff0c;让…

灾备建设中的网络传输

对于建设灾备系统&#xff0c;只要是网络可达即可进行数据备份保护。灾备中用的传输方式有很多种&#xff0c;比如网络传输&#xff0c;lan-free传输&#xff0c;网络加密传输等。 在这里给大家介绍下网络传输&#xff0c;灾备中的网络传输和平时大家熟知的是一样的。是指用一…

LSM Tree 深度解析

我们将深入探讨日志结构合并树&#xff0c;也称为LSM Tree&#xff1a;这是许多高度可扩展的NoSQL分布式键值型数据库的基础数据结构&#xff0c;例如Amazon的DynamoDB、Cassandra和ScyllaDB。这些数据库的设计被认为支持比传统关系数据库更高的写入速率。我们将看到LSM Tree如…

【Vue】vue2与netcore webapi跨越问题解决

系列文章 C#底层库–记录日志帮助类 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/124187709 文章目录 系列文章前言一、技术介绍二、问题描述三、问题解决3.1 方法一&#xff1a;前端Vue修改3.2 方法二&#xff1a;后端允许Cors跨越访问 四、资源…