算法学习:快速排序

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

    • 🚀 引言
    • 📌 快速排序算法核心思想
      • 1. 选择基准值(Pivot)
      • 2. 分区操作(Partitioning)
      • 3. 递归排序子序列
    • 📌 JavaScript 实现
      • 1. 快速排序主函数
      • 2. 分区函数
      • 3. 示例代码
    • 📌 优化建议
      • 1. 三数取中法
      • 2. 小数组时切换排序算法
      • 3. 尾递归优化
      • 4. 并行化处理
    • 📌 时间复杂度分析
    • 📌 总结

🚀 引言

快速排序(Quick Sort)是一种高效的排序算法,由计算机科学界的传奇人物托尼·霍尔(Tony Hoare)于1960年巧妙地提出。这一算法的核心智慧在于运用了经典的分治法策略——犹如古代兵法中的“分而治之”,将一个错综复杂的大列表分割成两个相对简单的子列表,随后对这两个子列表施以同样的策略,直到每个子列表都只剩下单一元素或为空,此时整个序列自然归于有序。此过程宛如构建一座秩序井然的金字塔,自下而上,层层推进。


📌 快速排序算法核心思想

在这里插入图片描述

1. 选择基准值(Pivot)

这是算法流程的起点,从数列中精心挑选出一个元素,赋予它一个特殊角色——“基准”(pivot)。基准的选择可以很灵活,但理想情况下应倾向于选择一个能将数据集大致均匀分割的值,以促进算法效率。

2. 分区操作(Partitioning)

分区操作是快速排序的精髓所在。其目标是在遍历数列一次的过程中,通过交换元素位置,使得所有小于基准值的元素都位于基准之前,而所有大于基准值的元素都排列在其后相等的元素可以放置在任一侧,完成这一操作后,基准值恰好位于其最终排序后的位置,即序列的中间。这一巧妙划分不仅为后续递归奠定了基础,也直接体现了快速排序分而治之的哲学。

3. 递归排序子序列

基于分区结果,数列被明确划分为两个独立的部分:左侧全部小于基准值,右侧则大于基准值。接下来,算法会对这两个子序列递归地应用同样的排序逻辑。通过不断地将问题规模减半,直到每个子序列只剩下一个或零个元素(这时自然视为已排序),整个数列便会在这一系列递归调用中逐步构建出全局的有序状态。这一策略确保了快速排序高效利用了分治策略的优势,尤其是在平均情况下展现出极高的效率。


📌 JavaScript 实现

1. 快速排序主函数

function quickSort(arr, left = 0, right = arr.length - 1) {
  // 如果左指针小于右指针,说明还有未排序的区间
  if (left < right) {
    // 调用分区函数,返回pivot的索引,完成一次分区
    const pivotIndex = partition(arr, left, right);
    
    // 对pivot左边的子数组进行快速排序
    quickSort(arr, left, pivotIndex - 1);
    // 对pivot右边的子数组进行快速排序
    quickSort(arr, pivotIndex + 1, right);
  }
  
  // 返回排序后的数组,实际上由于是原地排序,此处return并非必要
  return arr;
}

2. 分区函数

function partition(arr, left, right) {
  // 选择最右侧元素作为基准值pivot
  const pivot = arr[right];
  let i = left; // i为小于pivot的元素的边界,初始时指向最左侧
  
  // 遍历除了pivot外的所有元素
  for (let j = left; j < right; j++) {
    // 如果当前元素小于或等于pivot
    if (arr[j] <= pivot) {
      // 交换arr[i]和arr[j],并将i右移一位,保持i左侧的元素都小于等于pivot
      [arr[i], arr[j]] = [arr[j], arr[i]]; // ES6解构赋值进行交换
      i++; 
    }
  }
  
  // 最后将pivot(arr[right])与arr[i]交换,使得pivot位于正确的位置上
  [arr[i], arr[right]] = [arr[right], arr[i]];
  
  // 返回pivot的最终位置索引
  return i;
}

3. 示例代码

// 未排序数组示例
const unsortedArray = [3, 6, 8, 10, 1, 2, 1];

// 打印原始数组
console.log("Unsorted:", unsortedArray);

// 调用快速排序函数,得到排序后的数组
const sortedArray = quickSort(unsortedArray);

// 打印排序后的数组
console.log("Sorted:", sortedArray);

这段代码实现了快速排序算法,通过quickSort函数递归地将数组分为更小的子数组,并通过partition函数完成每部分的排序,最终达到整个数组有序的目的。代码中使用了ES6的解构赋值来简化元素交换的操作,使得代码更加简洁易读。


📌 优化建议

1. 三数取中法

  • 核心思想:通过更智能地选择基准值(pivot)来优化快速排序的性能。
  • 操作步骤
    1. 比较数组首部、中部、尾部的元素。
    2. 选取这三个数中的中位数作为分区操作的基准值。
    3. 这样做能有效平衡划分,即使在数据部分有序的情况下也能保持较好的性能,减少最坏情况的发生概率。

2. 小数组时切换排序算法

  • 适用条件:当待排序序列的元素数量较少时(例如少于10或15个)。
  • 策略详情:快速排序在小数组上的优势不明显,此时切换到插入排序等简单排序算法更为高效。因为插入排序在小数据集上具有较低的常数因子和无需递归的优点,能够快速完成排序,与快速排序形成互补。

3. 尾递归优化

  • 概念阐述:确保递归调用是函数的最后一个操作,便于某些支持该特性的编译器进行优化。
  • 实施方法:设计递归逻辑时,直接在递归调用的返回语句中返回计算结果,避免在递归返回后还需执行其他操作。
  • 注意事项:虽然JavaScript等语言不一定能自动优化尾递归,遵循此原则编写代码依然有助于提高可读性和未来跨平台移植的兼容性。

4. 并行化处理

  • 目标:利用多核CPU资源加速排序过程。
  • 实施步骤
    1. 将大数组分割成多个小块。
    2. 各个CPU核心独立并行地对分块数据执行快速排序。
    3. 最后合并各块已排序的结果。
  • 优势:在拥有多个处理器核心的系统上,此策略能显著缩短排序时间,尤其适合处理海量数据。

通过上述一系列优化措施,快速排序算法不仅在理论上保持了较高的时间效率,在实际应用中也变得更加灵活和健壮,能够有效应对各种规模数据集的排序挑战,展现出更高的性能和稳定性。


📌 时间复杂度分析

在这里插入图片描述

在这里插入图片描述

快速排序算法的性能极大程度上取决于基准值(Pivot)的选择策略。

  • 最优情况:若每次分区操作都能均匀地将数据集切分为两部分,每部分包含近似相等数量的元素,则递归树的深度为log₂n。鉴于每一层递归涉及遍历数组,总体操作计数约为n * log₂n。因此,快速排序在最佳情况下的时间复杂度为O(n log n),表现出高度的效率。

  • 最差情况:相反,若每次选取的基准值都导致极不均衡的分区,极端情形下每次仅将数组分为一个元素和剩余所有元素两部分,这将导致递归深度上升至n,伴随每次遍历数组的操作,时间复杂度恶化为O(n²),与冒泡排序相近。

  • 平均情况:在实践中,若假定分区大致均匀,即每次都能将数据集分为两个大小相似的子集,快速排序的平均时间复杂度同样为O(n log n)。这对于多数随机分布数据集而言,是一个非常高效的排序方案。

鉴于最坏情况下的性能瓶颈,实际部署快速排序算法时,往往配合采用基准值优化策略,比如“三数取中法”,来增强其鲁棒性和普遍适用性,确保在多种数据条件下仍能保持高效的排序性能。


📌 总结

快速排序算法通过分治法策略实现高效排序,其核心包括选择基准值、分区操作及递归排序子序列三大步骤。为了进一步提升性能和适应不同场景,可采纳诸如三数取中法优化基准选择、小数组时切换至插入排序、尾递归优化及并行处理等策略。这些优化不仅能够减少最坏情况出现的概率,还充分利用现代计算资源,使快速排序在实践中表现得更为出色,成为处理大量数据排序任务的优选算法之一。

总之,快速排序凭借其高效与灵活性,在众多排序算法中占据重要地位,广泛应用于各种数据排序需求之中。


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mac 安装 git

文章目录 前言一、介绍二、下载三、验证四、配置五、Git常用命令六、git提交和撤销工作流程代码提交和提交同步代码撤销和撤销同步 FAQ1.homebrew 下载解决方法一&#xff08;强烈推荐&#xff09;&#xff1a;解决方法二&#xff1a; 总结 前言 Git 是一个开源的分布式版本控…

Flutter问题 --- 用web端打开项目提示:The XMLHttpRequest onError callback was called.

跨域问题&#xff1a; 打开当前使用的flutter sdk,找到flutter\packages\flutter_tools\lib\src\web\chrome.dart文件并打开. 在--disable-extensions位置后添加--disable-web-security 找到flutter\bin\cache目录&#xff0c;删除flutter_tools.stamp和flutter_tools.snaps…

为什么说 Redis 是单线程的?——Java全栈知识(25)

为什么说 Redis 是单线程的&#xff1f; 我们常说的 Redis 是单线程的&#xff0c;但是我前面在讲持久化机制的时候又说 RDB 的持久化是通过主进程 fork 出一个子进程来实现 RDB 持久化。那么 Redis 到底是多线程还是单线程的呢&#xff1f; Redis 的网络 IO 和键值的读写是单…

一个简约高级视差效果PR动态图文开场视频模板

这是一个高质量且易于定制的pr模板。具有模块化结构&#xff0c;可以轻松更改内容。包括视频教程&#xff0c;即使是新手小白也可以轻松套用模板制作视频。 主要特点&#xff1a; 水平&#xff08;19201080&#xff09;和垂直&#xff08;10801920&#xff09;分辨率&#xff…

如何用PLC监听消防系统485总线通讯获取火灾报警数据

本文由艺捷自动化编写&#xff0c;艺捷自动化旗下产品有艺捷自动化网站和易为二维码说明书小程序&#xff08;微信&#xff09; 前言&#xff0c;先来说一下为什么会有这么一个奇怪的应用。在一个自动化系统改造升级项目中&#xff0c;甲方要求把消防的画面加到他们的后台上。…

判定字符是否唯一

题目链接 判定字符是否唯一 题目描述 注意点 0 < len(s) < 100s[i]仅包含小写字母 解答思路 首先想到的是使用数组存储字母是否出现过&#xff0c;如果多次出现则直接返回false为了不适用额外的数据结构&#xff0c;可以使用位运算判定字符是否唯一&#xff0c;思路…

Kibanna安装配置

环境&#xff1a;windows10、ES&#xff08;8.13.3&#xff09;、Kibana&#xff08;8.13.3&#xff09;、Logstash&#xff08;8.13.3&#xff09; 1.Kibanna安装配置 Kibanna对ES的数据进行可视化、分析和监控 Download Kibana Free | Get Started Now | ElasticDownload K…

如何让大模型更聪明?

如何让大模型更聪明&#xff1f; *随着人工智能技术的飞速发展&#xff0c;大模型在多个领域展现出了前所未有的能力&#xff0c;但它们仍然面临着理解力、泛化能力和适应性等方面的挑战。那么&#xff0c;如何让大模型变得更聪明呢&#xff1f; 方向一&#xff1a;算法创新 …

手写tomcat(Ⅱ)——Socket通信+tomcat静态资源的获取

Socket通信简介 参考文章&#xff1a;socket通讯原理及例程&#xff08;一看就懂&#xff09; socket是介于应用层&#xff08;http协议&#xff09;和传输层&#xff08;TCP/UDP协议&#xff09;之间的一层虚拟层 Socket是一个程序&#xff0c;符合TCP/UDP协议的规范&…

GNU Radio之OFDM Divide和Matrix Transpose底层C++实现

文章目录 前言一、OFDM Divide 模块1、简介2、模块作用3、参数意义4、C 具体实现 二、Matrix Transpose 模块1、简介2、参数意义3、C 具体实现 前言 gr-radar 中的 OFDM Divide 模块是GNU Radio中的一个组件&#xff0c;专门用于处理正交频分复用&#xff08;OFDM&#xff09;…

力扣HOT100 - 72. 编辑距离

解题思路&#xff1a; 动态规划 class Solution {public int minDistance(String word1, String word2) {int n1 word1.length();int n2 word2.length();int[][] dp new int[n1 1][n2 1];for (int j 1; j < n2; j) dp[0][j] dp[0][j - 1] 1;for (int i 1; i < …

【Android安全】AOSP版本对应编号| AOSP版本适配Pixel或Nexus型号 | 驱动脚本下载地址

AOSP版本对应编号 https://source.android.com/docs/setup/about/build-numbers?hlzh-cn#source-code-tags-and-builds 例如android-8.1.0_r1 对应的编号是OPM1.171019.011 可以适配Pixel 2 XL AOSP驱动脚本下载 编译AOSP时&#xff0c;需要Google的驱动&#xff0c;后面才…

SaToken+SpringBoot+Redis前后端分离登录认证

目录 前言一、创建工程项目&#x1f38d;1.1 创建后端工程1.2 创建前端工程 二、业务代码&#x1f38a;后端代码前端代码 三、测试参考资料 前言 Sa-Token 是一款 Java 语言的权限认证框架&#xff0c;提供了灵活、高效、易用的权限认证和会话管理功能。它是 SpringBoot、Spri…

解除网页禁止选择

控制台输入以下命令 复制&#xff1a;javascript:void(document.body.οncοpy) 可选&#xff1a;javascript:void(document.body.onselectstart) 拖拉&#xff1a;javascript:void(document.body.οnmοuseup)

短剧系统源码解析与应用

在数字化时代&#xff0c;短剧作为一种新兴的娱乐形式&#xff0c;因其内容紧凑、节奏快速而受到广大年轻群体的喜爱。短剧系统源码的开发和应用&#xff0c;不仅为创作者提供了一个展示才华的平台&#xff0c;也为观众带来了全新的观看体验。本文将对短剧系统源码进行解析&…

MVSnet 代码详解(pytorch)

大致过一下MVSnet 论文中核心的点对应代码应该怎么写。 forward 函数需要 照片&#xff0c;映射矩阵&#xff0c;以及深度值。 照片的shape是 &#xff08;1&#xff0c;5,3&#xff0c;1184,1600&#xff09;代表着1个batch,5张图片&#xff0c;然后一次是每张图片的channel和…

【软考】设计模式之装饰器模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.动态地给一个对象添加一些额外的职责。2.Decorator Pattern。3.就增加功能而言&#xff0c;装饰器模式比生成子类更加灵活。4.一种在不改变现有对象结构的情况下&#xff0c;动态地给对…

quartz定时任务

Quartz 数据结构 quartz采用完全二叉树&#xff1a;除了最后一层每一层节点都是满的&#xff0c;而且最后一层靠左排列。 二叉树节点个数规则&#xff1a;每层从左开始&#xff0c;第一层只有一个&#xff0c;就是2的0次幂&#xff0c;第二层两个就是2的1次幂&#xff0c;第三…

Go 和 Delphi 定义可变参数函数的对比

使用可变参数函数具有灵活性、重用性、简化调用等优点&#xff0c;各个语言有各自定义可变参数函数的方法&#xff0c;也有通用的处理方法&#xff0c;比如使用数组、定义参数结构体、使用泛型等。 这里总结记录一下 go、delphi 的常用的定义可变参数函数的方式&#xff01; 一…

Docker安装MongoDB(Linux版)

文章目录 前言一、Docker环境的准备1.安装依赖2.安装Docker 二、使用Docker安装MongoDB1.mongo版本选取2.拉取合适的镜像3.宿主机创建MongoDB需要挂载的文件夹4.第一次无认证创建mongo用户5.启动需要认证的mongo容器 问题汇总总结 前言 本文章主要介绍在Centos系统&#xff0c…