排序-快速排序(Quick Sort)

快排的简介

快速排序(Quick Sort)是一种高效的排序算法,采用分治法的策略,其基本思想是选择一个基准元素,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

快速排序的基本步骤:

  1. 选择基准:从数组中挑选一个元素作为“基准”(pivot)。
  2. 分区操作(Partitioning):
    • 将所有小于基准的元素放置在基准之前,所有大于基准的元素放置在基准之后。这个操作结束后,基准元素就处于数组的中间位置,此位置称为“分割点”。
    • 分区操作完成后,基准元素就位于其最终排序后的位置。
  3. 递归排序
    • 对基准元素左边的子数组递归执行快速排序。
    • 对基准元素右边的子数组递归执行快速排序。

快速排序的特点:

  • 时间复杂度
    • 最好情况(每次划分都很均匀):O(n log n)。
    • 平均情况:O(n log n)。
    • 最坏情况(每次划分都将数组分成一个元素与剩余所有元素两部分,例如已排序或逆序数组):O(n^2)。但通过随机选取基准或使用三数取中法等技巧可大幅降低这种情况发生的概率。
  • 空间复杂度
    • O(log n),主要来自于递归调用栈的深度。
  • 稳定性
    • 快速排序不是稳定的排序算法,因为在交换过程中相等的元素可能会改变原有的相对顺序。

快排的Java代码实现如下所示:

/* 元素交换 */
void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;
}/* 哨兵划分 */
int partition(int[] nums, int left, int right) {// 以 nums[left] 为基准数int i = left, j = right;while (i < j) {while (i < j && nums[j] >= nums[left])j--;          // 从右向左找首个小于基准数的元素while (i < j && nums[i] <= nums[left])i++;          // 从左向右找首个大于基准数的元素swap(nums, i, j); // 交换这两个元素}swap(nums, i, left);  // 将基准数交换至两子数组的分界线return i;             // 返回基准数的索引
}
/* 快速排序 */
void quickSort(int[] nums, int left, int right) {// 子数组长度为 1 时终止递归if (left >= right)return;// 哨兵划分int pivot = partition(nums, left, right);// 递归左子数组、右子数组quickSort(nums, left, pivot - 1);quickSort(nums, pivot + 1, right);
}

 快排流程图如下所示:

快排优化1:基数优化

快速排序在某些输入下的时间效率可能降低。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 𝑛−1、右子数组长度为 0 。我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),并将这三个候选元素的中位数作为基准数。这样一来,基准数“既不太小也不太大”的概率将大幅提升。

Java示例代码如下:

/* 选取三个候选元素的中位数 */
int medianThree(int[] nums, int left, int mid, int right) {int l = nums[left], m = nums[mid], r = nums[right];if ((l <= m && m <= r) || (r <= m && m <= l))return mid; // m 在 l 和 r 之间if ((m <= l && l <= r) || (r <= l && l <= m))return left; // l 在 m 和 r 之间return right;
}/* 哨兵划分(三数取中值) */
int partition(int[] nums, int left, int right) {// 选取三个候选元素的中位数int med = medianThree(nums, left, (left + right) / 2, right);// 将中位数交换至数组最左端swap(nums, left, med);// 以 nums[left] 为基准数int i = left, j = right;while (i < j) {while (i < j && nums[j] >= nums[left])j--;          // 从右向左找首个小于基准数的元素while (i < j && nums[i] <= nums[left])i++;          // 从左向右找首个大于基准数的元素swap(nums, i, j); // 交换这两个元素}swap(nums, i, left);  // 将基准数交换至两子数组的分界线return i;             // 返回基准数的索引
}

快排优化2:尾递归优化

尾递归优化是编程中一种优化技术,旨在减少递归调用的开销,尤其是当递归调用是函数体中的最后一个操作时。在快速排序算法中,通常有两个递归调用,分别处理基准元素左侧和右侧的子数组。尾递归优化尝试减少这种递归调用的栈消耗,但需要注意的是,Java等许多现代编程语言默认并不自动执行尾递归优化。尽管如此,我们可以通过手动调整快速排序的递归结构来模拟尾递归的效果,减少递归深度。

在某些输入下,快速排序可能占用空间较多。以完全有序的输入数组为例,设递归中的子数组长度为 𝑚 ,每轮哨兵划分操作都将产生长度为 0 的左子数组和长度为 𝑚−1 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 𝑛−1 ,此时需要占用 𝑂(𝑛) 大小的栈帧空间。

为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度,仅对较短的子数组进行递归。由于较短子数组的长度不会超过 𝑛/2 ,因此这种方法能确保递归深度不超过 log⁡𝑛 ,从而将最差空间复杂度优化至 𝑂(log⁡𝑛) 。

为了优化,我们可以使用循环而不是直接递归来控制递归过程,这样做的目的是尽量复用当前函数栈帧,避免每次递归调用都生成新的栈帧。代码如下所示:

/* 快速排序(尾递归优化) */
void quickSort(int[] nums, int left, int right) {// 子数组长度为 1 时终止while (left < right) {// 哨兵划分操作int pivot = partition(nums, left, right);// 对两个子数组中较短的那个执行快速排序if (pivot - left < right - pivot) {quickSort(nums, left, pivot - 1); // 递归排序左子数组left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right]} else {quickSort(nums, pivot + 1, right); // 递归排序右子数组right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1]}}
}

对两个子数组中较短的那个执行快速排序,可以这样理解其好处:

  • 减少递归深度:因为处理完较短的子数组后,再次进入递归时,处理的子数组规模相对减小,有助于控制递归调用的总深度。
  • 平衡负载:在某些情况下,比如数组已经部分排序,若总是先处理较短的子数组,可以在一定程度上避免递归树严重倾斜,使得递归调用更均匀地分布在左右两侧,从而更高效地利用递归栈空间。

这个策略是一种平衡快速排序递归深度的方法,尤其是在处理大数据集或递归深度受限的环境下尤为重要。通过这样的策略,可以提高算法在极端情况下的健壮性,减少潜在的栈溢出风险。

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

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

相关文章

Android 老年模式功能 放大字体

1 配置属性 <attr name"text_size_16" format"dimension"/><attr name"text_size_18" format"dimension"/><attr name"text_size_14" format"dimension"/><attr name"text_size_12&quo…

容器组件:Column ,Row(HarmonyOS学习第四课【4.1】)

容器组件-Column Column 容器组件是沿垂直方向布局的容器。该组件从APIVersion7开始支持从API version 9开始&#xff0c;该接口支持在ArkTs,卡片中使用。其可以包含子组件 Column(value?: {space?: string | number}) 参数 space 参数类型string | number 是否必填&am…

第11节 多文件函数生成位置规律

我把多年的shellcode开发经验浓缩在了这个专题课&#x1f469;&#x1f3fb;‍&#x1f4bb;里&#xff0c;主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料&#xff0c;内容里面的每一个环境我都亲自测试实操过的记录&#xff0c;有需要的小伙伴可以参…

进程创建-fork

demo1代码状态变迁 demo2代码输出到stdout使用管道 demo1 代码 #include <pthread.h> #include <iostream> #include <unistd.h> int main(int argc, char const *argv[]) {// 1.pid_t x fork();// 2.pid_t y fork();// 3.printf("%d %d\n", x…

【C++】STL-list的使用

目录 1、list的使用 1.1 list的构造 1.2 list的遍历 1.3 list capacity 1.4 list element access 1.5 容量相关 list是一个带头双向循环链表 1、list的使用 1.1 list的构造 1.2 list的遍历 list只有两种遍历方式&#xff0c;因为没有operator[] 因为list的双向链表&am…

《建筑抗震设计规程》DB62/T 3055-2020下载

本规程适用于抗震设防分类为乙类、丙类&#xff0c;抗震设防烈度为6度(0.05g)、7度(0.10g、0.15g)、8度(020g、030g)、9度(0.40g)的多高层钢筋混凝土结构及钢-混凝土混合结构、中等跨度钢屋盖结构、门式刚架钢结构、多低层砖砌体结构房屋建筑的抗震设计。 关于甘肃省地方标准《…

01 | 为什么需要消息队列?

哪些问题适合使用消息队列来解决&#xff1f; 1. 异步处理 2. 流量控制 使用消息队列隔离网关和后端服务&#xff0c;以达到流量控制和保护后端服务的目的。 3. 服务解耦 无论增加、减少下游系统或是下游系统需求如何变化&#xff0c;订单服务都无需做任何更改&#xff0c…

【主题广泛|稳定检索】2024年社会科学、公共服务与人文艺术国际会议(SPSHA 2024)

2024年社会科学、公共服务与人文艺术国际会议&#xff08;SPSHA 2024&#xff09; 2024 International Conference on Social Sciences, Public Services, and Humanities and Arts 【会议简介】 本次会议定于2024年在中国的繁华都市——广州召开&#xff0c;汇聚了全球在该领…

如何挑选护眼灯?分享最好的台灯品牌排行榜

作为家长&#xff0c;孩子的健康无疑是我们最为牵挂的事项。然而&#xff0c;通过研究数据显示&#xff0c;我国青少年儿童的近视率高达52.7%&#xff0c;这意味着在每十名儿童中&#xff0c;就有超过半数的孩子可能面临视力问题。这一数据无疑令人警觉。当我们看到孩子们在写作…

Web3加密空投入门:空投类型有哪些?如何避免限制?

今天分享空投如何避免限制以提高效率&#xff0c;增加成功几率&#xff0c;首先我们来了解什么是空投加密&#xff0c;有哪些空投类型。 一、什么是空投加密&#xff1f; 加密货币空投是一种营销策略&#xff0c;包括向用户的钱包地址发送免费的硬币或代币。 加密货币项目使用…

中医揿针的注意事项

点击文末领取揿针的视频教程跟直播讲解 关于揿针的注意事项&#xff0c;我们可以从以下几个方面进行探讨&#xff1a; 01操作前准备 1. 确保针具的清洁和无菌状态&#xff0c;以避免感染。 2. 了解患者的身体状况&#xff0c;如是否有特殊疾病或过敏史&#xff0c;以便选择…

解码管理新趋势:咨询公司如何助力企业破局升级?

随着数字化、智能化技术的不断发展&#xff0c;企业管理的边界正在被重新定义。传统的层级管理正在向扁平化、网络化转变&#xff0c;员工的参与度和自主性得到了前所未有的提升。同时&#xff0c;企业也更加注重数据驱动决策&#xff0c;通过大数据分析来洞察市场变化&#xf…

STM32 PWM 计数器模式和对齐

STM32 PWM 计数器模式和对齐 1. TIM高级定时器简介2. TIM计数模式2.1 向上计数2.2 向下计数2.3 中心对齐模式&#xff08;向上/向下计数&#xff09;2.4 重复计数 3. PWM输出模式3.1 举例看下PWM中心对齐模式&#xff0c;设置参数如下&#xff1a; 4. FOC中PWM相关设置说明4.1 …

SpringBoot对接微信公众平台(1)--- 配置微信公众平台测试号URL并校检

SpringBoot对接微信公众平台&#xff08;1&#xff09;--- 配置微信公众平台测试号URL并校检 说明微信公众号接口测试号申请后端代码实现内网穿透接口测试 说明 这里记录下自己学习SpringBoot对接微信公众平台的成长过程&#xff0c;以防止后面继续踩坑且方便以后直接使用。这…

房价暴跌到头?中国楼市回调信号愈发强烈!

一、房地产市场的繁荣与萧条 在过去的几十年里&#xff0c;中国的房地产市场经历了多次繁荣与萧条的周期。自2015年第二季度起&#xff0c;全国房价开始逐渐攀升&#xff0c;这标志着新一轮房地产市场的繁荣期的开始。以深圳为例&#xff0c;新政出台后&#xff0c;这座城市的…

CSS 实现文本的渐变色

定义一个类 .text-color{/* 创建一个水平方向的颜色渐变 */background: linear-gradient(120deg, #bd34fe 30%,#5c34fe, #41d1ff);/* 将文本透明度设置为0&#xff0c;以便背景渐变可见 */color: transparent;/* 使用背景渐变来填充文本背景 */-webkit-background-clip: text;…

LeetCode 0994.腐烂的橘子:广度优先搜索(BFS)

【LetMeFly】994.腐烂的橘子&#xff1a;广度优先搜索(BFS) 力扣题目链接&#xff1a;https://leetcode.cn/problems/rotting-oranges/ 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子…

【Spring】GoF 之代理模式

一、代理模式 在 Java 程序中的代理模式的作用&#xff1a; 当一个对象需要受到保护的时候&#xff0c;可以考虑使用代理对象去完成某个行为 需要给某个对象的功能进行功能增强的时候&#xff0c;可以考虑找一个代理进行增强 A 对象无法和 B 对象直接交互时&#xff0c;也可以…

如何在 Python 中使变量不可继承

1. 问题背景 在 Python 中&#xff0c;子类可以继承父类的属性和方法。但是&#xff0c;有时我们希望子类不能继承父类的某些属性或方法。这种情况下&#xff0c;该如何做呢&#xff1f; 2. 解决方案 解决方案一&#xff1a;使用双下划线前缀 Python 中的双下划线前缀用于表…

仓库管理流程详解(附作业流程图)

仓库管理流程在企业的日常运营中至关重要。它不仅是物资流转的核心环节&#xff0c;更关乎着企业的运营效率、成本控制和客户服务水平。一个高效、规范的仓库管理流程能够确保货物从入库到出库的各个环节有序进行&#xff0c;减少资源浪费和时间成本&#xff0c;同时帮助企业实…