排序-快速排序(Quick Sort)

news2024/11/29 3:59:04

快排的简介

快速排序(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.coloradmin.cn/o/1670526.html

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

相关文章

aop获取方法运行时间

Slf4j Component Aspect//aop类 public class TimeAspect {/*Around 环绕通知 用ProceedingJoinPoint Before 前置通知 用JoinPoint&#xff08;是ProceedingJoinPoint的父类型&#xff09;&#xff0c;用它可以获得方法执行的相关信息&#xff0c;如目标类名&#xff0c;方法…

我是如何免费抵御一个多月的 DDos/CC 攻击的?

今天明月给大家详细分享一下我的博客是如何免费抵御了长达一个多月的 DDos/CC 攻击的&#xff0c;在【现在 DDos/CC 攻击门槛低的可怕&#xff01;】一文里明月就说过现在 DDos/CC 攻击几乎是没有门槛的&#xff0c;任何一个老鼠屎在群里看到你的博客都可以轻松便捷的发动一次 …

ADOP带你了解:800G OSFP光收发器:演进发布

引言 随着云计算、大数据和人工智能等技术的不断进步&#xff0c;对数据中心的带宽和处理能力要求也在不断提高。在这样的背景下&#xff0c;800G OSFP光收发器的问世&#xff0c;为我们提供了更高速度和更高效率的数据传输解决方案。 800G OSFP光收发器演进路线 路线 1&…

CSS3私有前缀+新增盒模型相关属性+新增背景属性(如果想知道CSS3私有前缀、新增盒模型相关属性的知识点,那么只看这一篇就足够了!)

前言&#xff1a;CSS3 是CSS2 的升级版本&#xff0c;它在CSS2 的基础上&#xff0c;新增了很多强大的新功能&#xff0c;从而解决一些实际面临的问题。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 先让我们看一下本篇文章的…

vue3 自定义调控参数-简易生成器

vue3 自定义调控参数-简易生成器 文章目录 封装生成器控件如下父组件使用如下&#xff1a; 此功能好像是某厂的在线视频笔试题&#xff0c;当时写完也没有结果。。。我觉得此 demo 适用场景&#xff1a;自定义动态表单时需要定制字段、用户自定义信息等 封装生成器控件如下 /…

阿里云短信提示被攻击怎么解决!!

你是否收到过这样的短信&#xff0c;【阿里云】尊敬的用户&#xff1a;您的IP: 实例名称&#xff1a; 受到攻击流量已超过云盾DDoS基础防护的带宽峰值&#xff0c;服务器的所有访问已被屏蔽&#xff0c;如果35分钟后攻击停止将自动解除否则会延期解除。详情请登录云盾控制台DDo…

SPSS之主成分分析

SPSS中主成分分析功能在【分析】--【降维】--【因子分析】中完成&#xff08;在SPSS软件中&#xff0c;主成分分析与因子分析均在【因子分析】模块中完成&#xff09;。 求解主成分通常从分析原始变量的协方差矩阵或相关矩阵着手。 &#xff08;1&#xff09;当变量取值的度量…

MySQL中的子查询

子查询,在一个查询语句中又出现了查询语句 子查询可以出现在from和where后面 from 表子查询(结果一般为多行多列)把查询结果继续当一张表对待 where 标量子查询(结果集只有一行一列)查询身高最高的学生,查询到一个最高身高 列子查询(结果集只有一行多列) 对上表进行如下操作 …

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 …

webpack如何自定义一个loader

我们在使用脚手架的搭建项目的时候往往都会帮我们配置好所需的loader&#xff0c;接下来讲一下我们要如何自己写一个loader应用到项目中&#xff08;完整代码在最后&#xff09; 1. 首先搭建一个项目并找到webpack配置文件&#xff08;webpack.config.js&#xff09; 在modul…

arcgis_滑坡易发性评价数据处理过程

arcgis_LSM数据处理过程 地形因子处理环境因子处理获取坐标点的方法arcgis问题arcgis进行克里格插值更改投影方式中国地质数据下载站python矢量转栅格重采样设置像元大小一致,设置环境保证栅格对齐 地形因子处理 原始数据:DEM Elevation: 重采样 Slope、Aspect 设置环境保障…

java入门详细教程——day01

目录 1. Java入门 1.1 Java是什么&#xff1f; 1.2 Java语言的历史 1.3 Java语言的分类 1.4 Java语言的特点 1.4.1 先编译再解释运行 1.4.2 跨平台 1.5 JRE和JDK&#xff08;记忆&#xff09; 1.6 JDK的下载和安装&#xff08;应用&#xff09; 1.6.1 下载 1.6.2 安…

四款不同类型的企业防泄密软件推荐

在数字化快速发展的今天&#xff0c;企业数据的安全与保密显得愈发重要。防泄密软件作为一种专门的数据保护工具&#xff0c;已经逐渐成为企业不可或缺的安全屏障。本文将深入探讨防泄密软件对企业的意义&#xff0c;并介绍一些市面上主流的防泄密软件。 首先&#xff0c;防泄密…

redis的跳表

typedef struct zskiplistNode {// 分值double score;// 成员对象robj *obj;// 后退指针struct zskiplistNode *backward;// 层struct zskiplistLevel {// 前进指针struct zskiplistNode *forward;// 跨度unsigned int span;} level[]; } zskiplistNode;跳表的节点查找算法可以…

Springboot自动装配源码分析

版本 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --> </par…

【线程创建】——三种方式➕多线程案例练习

02 线程创建 Thread , Runnable , Callable 三种创建方式 Thread class - 继承Thread类 (重点) Runnable接口 - 实现Runnable接口 (重点) Callable接口 - 实现Callable接口 (了解) Thread 类实现 它继承了老祖宗 Object java.lang.Object java.lang.Thread 它实现了 Runnab…

有手就会做!保姆级Jmeter分布式压测操作流程(图文并茂)

分布式压测原理 分布式压测操作 保证本机和执行机的JDK和Jmeter版本一致配置Jmeter环境变量配置Jmeter配置文件 上传每个执行机服务jmeter chmod -R 755 apache-jmeter-5.1.1/ 执行机配置写自己的ip 控制机配置所有执行机ip,把server.rmi.ssl.disable改成true 将本机也作为压…

ansible -playbook运维工具、语法、数据结构、命令用法、触发器、角色

目录 配置文件 基本语法规则&#xff1a; YAML支持的数据结构 playbook核心元素 ansible-playbook用法&#xff1a; 触发器 特点&#xff1a; 角色&#xff1a; 习题&#xff1a; 配置文件 playbook配置文件使用yaml语法&#xff0c;YAML 是一门标记性语言,专门用来写配…

QT函数整理

目录 1. 适应高分辨率函数 1. 适应高分辨率函数 自动适应调整设备安装QT的UI分辨率&#xff1a; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 加载位置&#xff1a;

主机扫漏:Apache Tomcat 环境问题漏洞(CVE-2023-46589)

文章目录 引言I 修复此安全问题see also引言 Apache Tomcat存在环境问题漏洞,该漏洞源于存在不正确的输入验证漏洞,可能会导致将单个请求视为多个请求,从而在反向代理后面出现请求走私。 Tomcat did not correctly parse HTTP trailer headers. A specially crafted traile…