数据结构(邓俊辉)学习笔记】排序 1——快速排序:算法A

文章目录

  • 1. 分而治之
  • 2. 轴点
  • 3. 构造轴点
  • 4. 单调性 + 不变性
  • 5. 实例

1. 分而治之

在这里插入图片描述

主题就是排序。实际上我们对于排序问题并不陌生。你应该记得在最开始的几章,我们就分别介绍过起泡排序、插入排序、选择排序以及归并排序,而在介绍散列技术时,我们也曾介绍过桶排序、计数排序以及基数排序。在讨论优先级队列时,也结合堆这种结构,介绍过堆排序以及更为通用的锦标赛排序。因此在本章中,我们将进而重点的学习若干种高级的排序算法,并讨论与之相关的几个衍生问题。在接下来的第一节,就让我们首先来学习快速排序算法。

在这里插入图片描述

快速排序 (quicksort) 是霍尔爵士在上世纪60年代发明的一种算法。这也是基于分治策略的又一典型算法。具体来说,对于任何一个待排序列,这里也需要将它们分为前后两个子序列,并对这两个规模更小的子序列递归的实施排序。

听到这个思路,你或许会想起归并排序。是的,quicksort 和 mergesort 都采用了分治策略,但二者又有很大的区别,比如对于快速排序来说,子问题之间的独立性更为鲜明。比如这里要求前一序列中的任何元素在数值上都不得超过后一序列中的任意元素,这是一个非常强的条件。如果这条件的确满足,那么在分别递归的对前一序列和后一序列进行排序之后,只要将二者简单地串接起来,也就自然得到了整体的有序序列,从而完成最初的排序任务。

当然,与归并排序一样,只含单个元素的序列自身就是有序的,因此也可以作为平凡的递归基。

由上可见,按照霍尔爵士的设想,只要能够完成这种左小右大式的子序列划分,那么剩余的工作可以完全地交给递归来完成。因此,对于快速排序来说,核心的任务与难点在于如何完成子任务或子序列的划分。

从这点来看,归并排序恰好相反。我们知道,对于归并排序算法而言,其计算量以及难点都在于如何将子任务的解进行合并。那么霍尔爵士所设想的这种划分,具体的又当如何实现呢?

2. 轴点

在这里插入图片描述

为了实现霍尔爵士所设想的划分,我们需要借助轴点。

所谓的轴点 pivot 是在序列中的某一类特殊元素。这类元素的特征是:凡是居于它左侧的元素都不比它更大。对称的居于它右侧的元素也不比它更小。因此如果用高度来表示元素的数值大小,那么相对于轴点所对应的这条水平线,左侧的元素都位于下方,而右侧元素都位于上方。

不难看出,以任何一个轴点为界,整个序列总是可以分为左小右大的两个子序列。而这正是霍尔爵士所设想的那种左小右大式的划分。

在这里插入图片描述
因此,只要我们能够在任何一个序列中快速地找到其中的轴点,那么借助二分式的递归,我们就自然可以导出快速排序的完整算法。由此我们也再一次更为清晰地看到,快速排序算法的核心就在于如何快速地确定轴点。

因此我们接下来需要实质讨论的重点也无非就是这样一个快速划分的算法。
在这里插入图片描述
然而在通往快速划分算法 partition 的道路上,我们首先就会遇到一个拦路虎。因为我们不能保证在任何一个待排序的序列中,轴点元素总是存在的。实际上既然相对于轴点,所有的元素都是按照前小后大的次序排列的,所以轴点自身必然是已经就位了,它在当前序列中所对应的秩,也就是它最终在有序列中所对应的秩。是的,轴点必然是就位的,这是一项非常强的必要条件。

实际上每一个元素都有可能天生不具备这个条件。任何元素都非就位的序列普遍存在,实际上它们也就是所谓的乱排序列 derangement。

比如任何一个有序序列,只要经过一次循环移位,就可得到一个这样的乱排序。

不难理解在完全有序的序列中,所有的元素自身都是一个轴点。而反过来,如果一个序列中的所有元素都是轴点,那么它也自然是有序的。

从这个角度来看,所谓的快速排序无非就是将原序列中的所有元素逐个地转换为轴点的过程。

尽管在任意序列中,轴点未必天然的存在,但好消息是,只要适当地交换元素的位置,我们总是可以将任何一个元素转化为一个轴点。

那么具体的又当如何交换呢?为此我们又需要付出多高的成本呢?

3. 构造轴点

在这里插入图片描述
霍尔爵士所设计的轴点构造算法,其原理和过程可以由这幅图来示意。

首先我们要选取一个轴点候选作为培养对象,通常我们都不妨取做这个序列的首元素,而在整个构造的过程中,我们都需要用到 lo 与 hi 两个指针,这两个指针将整个序列分为 L,U 和 G 三部分。

这里的 L 是一个前缀,其中的任何一个元素在数值上都不超过轴点的候选。对称的 G 是一个后缀,其中的任何一个元素在数值上也不会小于轴点候选,而居于二者之间的子序列 U, 则由大小仍然未知的元素构成。

在初始状态下,U 也就是整个序列,而 L 和 G 都是空的。在算法启动之后,我们会尝试着将 lo 与 hi 交替地向内侧移动,从而令它们彼此靠近。lo 每向后移动一步,L 也就会向后扩展一个单位。对称的, hi 每向前移动一步,G 也会向前拓展一个单元。

为了完成这种拓展,我们需要适当地将 U 中的某个元素加入到 L 或者 G 中,最终当 lo 与 hi 同时指向同一个位置时,我只需将此前选定的轴点候选者放到这个位置。那么这个候选者也就自然成为了一个名副其实的轴点。

4. 单调性 + 不变性

以下,就让我们通过这样一组插图,更为细致的考察和理解轴点构造算法的原理以及具体过程。
在这里插入图片描述

在这个过程中我们需要把握两条核心的不变性。首先正如我们此前所言,从数值上来看,子序列 L 中的元素都不超过轴点候选,同时子序列G中的元素也都不小于轴点候选。其次,对于子序列 U 而言,它的首元素和末元素总是交替的在逻辑上可以视作为空闲单元

  1. 我们首先来验证初始状态,比如在初始状态下,无论 L 或 G 都是空的,所以第一条自然满足。同样在初始条件下,U 的首元素已经作为轴点的候选被取出备份。因此它的确可以认为是空闲的。

  2. 再来考察一般情况下的 U,它的首元素为 lo,而末元素为 hi。不失一般性,假设此时的 lo 是空闲的,于是我们就可以尝试着向左侧拓展子序列 G。具体来说,只要当前 U 的末元素,也就是 hi, 在数值上不小于候选轴点,我们就可以简明地,通过令 hi 递减一个单位,从而将元素 hi归入到子序列 G 中。

  3. 接下来,如果新的末元素依然满足这样的条件,我们就继续将它归入到G 中,直到某个时刻末元受 hi 不再满足这个条件,也就是说此时的元素 hi 在数值上会严格的小于候选轴点。

    没错,严格小于候选轴点,这不正是此序列 L 所对应的入选条件吗?因此,在这种情况下,我们不妨将末元素 hi 转移至当前仍然空闲的单元 lo 中。尽管因此单元 lo 将不再是空前的,但相应的 hi 所腾出的那个单元又会随即变成空闲的。也就是说 U 所具有的不变性依然成立。

  4. 我们接下来的处理方向将与刚才恰好颠倒过来,也就说我们会进而去考察 U 的首元素,只要这个元素在数值上不超过候选轴点,我们就可以同样简明地令 lo 递增一个单位,从而将这个数元素归入到子序列 L 中。

    子序列 L 也会因此向后端拓展一个单元。以下同理,只要首元素在数值上依然不超过候选轴点,我们都会同样地将它归入到子序列 L 中。这样的情况出现多少次,此序列 L 就会向后拓展多少个单元。

    子序列 L 的这种拓展会在什么时候终止呢?没错,也就是接下来的首元素 lo 在数值上不再是继续地不超过候选轴点。而这意味着什么呢?没错,这意味着此时的首元素 lo 完全符合子序列 G 的入选条件。

  5. 因此我们不妨将它转移到当前仍是空闲的那个单元 hi 中。而此后,尽管单元 hi 不再是空闲的,但是随着刚才那个元素的移出,单元 lo 又随即变成是空闲的了。也就是说 U 的不变性依然成立。

当然在经过以上的拓展之后,无论是子序列 L 还是子序列 G,在数值上也依然保持不变性。至此,整个算法经历了一个完整的周期,经过这样的一个周期,不仅不变性依然保持,而且更重要的是,我们可以注意到这里的单调性,更准确的讲是子序列长度的单调性。

因为我们看到子序列 L 和 G 的长度都有所增加,同时相应的子序列 U 的长度却在无形中缩短了。因此,当最终子序列 U 退换为只有一个单元时,也就是霍尔爵士所设想的,算法终止之前的临界状态。

到那个时候,我们只需将候选轴点植入于唯一的这个空闲单元,它就会成为一个名副其实的轴点。同时我们也完成了对原序列的一次快速划分,整个 partition 算法也可顺利结束。

5. 实例

以下就来通过这个具体实例,体会 partition 算法的具体过程。
在这里插入图片描述

  1. 这里的待排序序列由10个元素构成。在初始状态下,子序列 U 也就是整个序列。 按照通常的习惯,我们将首元素 6 取做待培养的候选轴点,在将它取出备份之后,对应的单元在逻辑上可以视作为是空闲的。

  2. 因此接下来,我们首先要尝试着去拓展子序列 G,虽然此时它还是空,为此我们总是要考察 U 的末元素,也就是此时的7。我们发现这个元素的确大于候选的轴点6,因此它的确可以归入子序列 G, 子系列 G 拥有了第一个元素,而子系列 U 则相应地减少了一个元素。

  3. 然而接下来,子序列 G 的拓展却不得不止步于新的末元素,因为我们注意到,这个元素的数值为 1,要严格的小于候选轴点 6。还记得我们刚才为此设计的处理方法吗?没错,既然此时的这个末元素更小,我们也就自然地可以将它归入到子序列 L 中。为此,我们只需将它转移至当前仍为空闲的首单元。

  4. 接下来,在拥有了第一个元素之后,子序列 L 也会试图继续向右拓展。很幸运,我们发现接下来的首元素 3 也要小于候选轴点。这就意味着我们同样可以将它归入到子序列 L 中。

  5. 然而接下来,子序列 L 的拓展也会止步于新的首元素,因为我们发现新的这个首元素在数值上是要大于候选轴点。

    解放心,对于这种情况,我们算法依然足以处理,难道不是吗?既然这个元素在数值上要超过候选轴点,所以它也自然可以归入子序列 G 中。而此时紧邻与子序列 G 的左侧恰好有一个空闲单元。因此接下来我们只需将这个更大的元素转移至这个空显的单元。如此,子序列 G 向前拓展一个单元,而子序列 U 也相应地减少了一个单元。同时在这个元素被转移之后,腾出来的首单元又继而被视作为一个空闲单元。也就是说算法不依然保持。

  6. 以下同理,子序列 G 的拓展会止步于元素 5b。于是我们不妨就将这个元素转移至当前空闲的首单元处,并转而去尝试拓展子序列 L。

  7. 随后在依次加入了元素 2 和 5a 之后,子序列 L 的拓展也会止步于元素9。再一次,我们可以将这个元素转移至当前为空的末单元,并在接下来转而去尝试拓展子序列 G。

  8. 很遗憾,我们尝试依然终止于更小的元素 4。因此我们必须将它转移至当前为空的首单元。至此整个子序列 U 的长度已经退化为 1。因此我们只需将候选的元素6 植入于其中,这个元素也就成为了一个名副其实的轴点。

    作为验证,你可以逐个地检查一下,在这个元素之前的所有元素的确都不比它大,而在它之后的所有的元素也的确都不比它小。

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

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

相关文章

自定义TextView实现结尾加载动画

最近做项目,仿豆包和机器人对话的时候,机器人返回数据是流式返回的,需要在文本结尾添加加载动画,于是自己实现了自定义TextView控件。 源码如下: import android.content.Context import android.graphics.Canvas imp…

基于云原生向量数据库 PieCloudVector 的 RAG 实践

近年来,人工智能生成内容(AIGC)已然成为最热门的话题之一。工业界出现了各种内容生成工具,能够跨多种模态产生多样化的内容。这些主流的模型能够取得卓越表现,归功于创新的算法、模型规模的大幅扩展,以及海…

用Boot写mybatis的增删改查

一、总览 项目结构: 图一 1、JavaBean文件 2、数据库操作 3、Java测试 4、SpringBoot启动类 5、SpringBoot数据库配置 二、配置数据库 在项目资源包中新建名为application.yml的文件,如图一。 建好文件我们就要开始写…

【MySQL00】【 杂七杂八】

文章目录 一、前言二、MySQL 文件1. 参数文件2. 日志文件3. 套接字文件4. pid 文件5. 表结构定义文件6. InnoDB 存储引擎文件 二、BTree 索引排序三、InnoDB 关键特性1. 插入缓冲1.1 Insert Buffer 和 Change Buffer1.1 缓冲合并 2. 两次写2. 自适应哈希索引3. 异步IO4. 刷新邻…

江协科技STM32学习- P9 OLED调试工具

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…

# VMware 共享文件

VMware tools快速安装 VMware 提供了 open-vm-tools,这是 VMware 官方推荐的开源工具包,通常不需要手动安装 VMware Tools,因为大多数 Linux 发行版(包括 Ubuntu、CentOS 等)都包含了 open-vm-tools,并且已…

Linux网络编程IO管理

网络 IO 涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段: 等待内核协议栈的数据准备就绪;将内核中的数据拷贝到用户态的…

Kafka【八】如何保证消息发送的可靠性、重复性、有序性

【1】消息发送的可靠性保证 对于生产者发送的数据,我们有的时候是不关心数据是否已经发送成功的,我们只要发送就可以了。在这种场景中,消息可能会因为某些故障或问题导致丢失,我们将这种情况称之为消息不可靠。虽然消息数据可能会…

Spring框架基础介绍2.0

目录 AOP概述 面向切面思想 优点: 核心原理: 使用案例: AOP 的基本概念 springAOP 实现 AspectJ 中常用的通知 Spring事物管理 数据库事务管理? spring 事务管理? Spring中的事物管理分为两种形式: 1、编程式事物管理 2、声明…

低空经济如此火爆,新手如何分一杯羹?

低空经济的火爆为新手提供了诸多参与和分一杯羹的机会。以下是一些具体的建议,帮助新手在这一领域找到切入点: 1. 了解行业概况与趋势 定义与范围:低空经济是指在3000米以下空域内进行各种有人和无人驾驶航空器活动的经济形态,涉…

dubbo的SPI机制

一.dubbo的SPI机制 SPI机制是一个服务发现机制,通过接口的全限定名找到指定目录下对应的文件,然后加载对应的实现类注册到系统中进行使用。 在Java原生跟mysql的驱动加载也使用了这个机制,但是他们只能进行全部实现类的加载(遍历…

最新HTML5中的文件详解

第5章 HTML5中的文件 5.1选择文件 可以创建一个file类型的input,添加multiple属性为true,可以实现多个文件上传。 5.1.1 选择单个文件 1.功能描述 创建file类型input元素,页面中不再有文本框,而是 选择文件 按钮,右侧是上次文件的名称&a…

数据分析面试题:客户投保问题分析

目录 0 场景描述 1 数据准备 2 问题分析 2.1 计算小微公司的平均经营时长 2.2 计算小微公司且角色为投保人,保险起期在18年的总保费 2.3 假设,DWD_CUSTOMER_REL客户关联关系表中,存在部分客户保单数很多,部分客户保单数很少的情况,此时DWD_CUSTOMER_BASE表关联,程序…

百度智能云向量数据库创新和应用实践分享

本文整理自第 15 届中国数据库技术大会 DTCC 2024 演讲《百度智能云向量数据库创新和应用实践分享》 在 IT 行业,数据库有超过 70 年的历史了。对于快速发展的 IT 行业来说,一个超过 70 年历史的技术,感觉像恐龙一样,非常稀有和少…

Anaconda Prompt 安装paddle2.6报错

bug描述 python 3.11.9 通过 pip install paddlepaddle2.6.1 安装后,运行 paddle.utils.run_check() 则出现下面的错误: 解决办法 方法一:使用paddle 3的版本 这里要注意我的python版本 方法二:使用低版本的python python3.9…

Lombok jar包引入和用法

大家好,今天分享一个在编写代码时的快捷方法。 当我们在封装实体类时,会使用set、get等一些方法。如下图,不但费事还影响代码的美观。 那么如何才能减少代码的冗余呢,首先lib中导入lombok的jar包并添加库。 此处我已导入&#xf…

Jenkins+Svn+Vue自动化构建部署前端项目(保姆级图文教程)

目录 介绍 准备工作 配置jenkins 构建部署任务 常见问题 介绍 在平常开发前端vue项目时,我们通常需要将vue项目进行打包构建,将打包好的dist目录下的静态文件上传到服务器上,但是这种繁琐的操作是比较浪费时间的,可以使用jenkins进行自动化构建部署前端vue 准备工作 准备…

《粮食科技与经济》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问:《粮食科技与经济》是不是核心期刊? 答:不是,是知网收录的第一批认定学术期刊。 问:《粮食科技与经济》级别? 答:省级。主管单位: 湖南省粮食和物资储备局 …

bat批处理实现从特定文件夹中提取文件内容并以父文件夹名存储

1、需求分析 标题是bat批处理实现从特定文件夹中提取文件内容并以父文件夹名存储。这里面我们要做的工作是: ①、批处理脚本使用的是bat文件; ②、文件夹下面有很多子文件夹,然后子文件夹下仍然有相同的文件结构,我们需要从三级…

halcon 自定义距离10的一阶导数幅图,摆脱sobel的3掩码困境

一,为什么要摆脱3的掩码 在处理图像的过程中,会用到平滑算子,很容易破坏边际,所谓的一阶导数sobel只计算掩码为3的差分,在幅度图分割中,往往是很难把握的。 举个例子-现在图像头平滑好了,缺陷…