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

news2024/11/13 8:53:36

文章目录

  • 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.coloradmin.cn/o/2116082.html

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

相关文章

Tableau 2023下载安装教程最新教学附软件包百度网盘分享链接地址

Tableau 2023介绍 Tableau 2023下载安装教程最新教学附软件包百度网盘分享链接地址,Tableau 是一款强大的数据可视化软件。它能连接多种数据源并整合,操作简单,通过拖放即可创建可视化报表和仪表盘。具有高效的分析处理能力,支持…

windows下安装并使用nvm

目录 一.准备工作:卸载node 卸载步骤 二.下载nvm 三.安装nvm 三.配置下载源【重要】 四.使用nvm安装node.js 五.nvm常用命令 六.卸载nvm 一.准备工作:卸载node 如果电脑上已经有node,那么我们需要先完全卸载node,再安装…

LeetCode 热题 100 回顾15

干货分享,感谢您的阅读!原文见:LeetCode 热题 100 回顾_力code热题100-CSDN博客 一、哈希部分 1.两数之和 (简单) 题目描述 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标…

FastAPI模块化:为复杂应用程序提供清晰的结构

开题描述: 在现代软件开发中,随着应用程序规模的扩大和功能的增加,传统的单体架构逐渐暴露出其局限性。FastAPI,作为一款高性能的现代Web框架,通过其模块化设计提供了一种解决方案。本文将探讨FastAPI模块化如何为构建…

顶刊中的树状图如何绘制?|科研绘图·24-09-07

小罗碎碎念 本期推文主题:树状图 本期推文主要介绍如何绘制树状图以及它的一些变体形式,看完本篇推文,你最终能够实现的效果如下。 一、组织结构图 Dendrogram是一种网络结构,由一个根节点开始,该节点通过边或分支连接…

找商业网字体加密(TTFont方法)

网点地址:公司介绍-泰州名列新材料有限公司 (zhaosw.com) 问题如下: 在网站中看到的电话号码在页面源码中无法查看 破解步骤: 1.找到woff文件 查找字体的class属性,全文查找font-face-encrypted找到如下内容,可以看到…

在 Linux 上部署javaWeb项目+图文详解_java web项目部署到linux服务器

-f : force强制的意思,如果目标文件已经存在,不会询问直接覆盖 -i : 若目标已经存在,就会询问是否覆盖 -u : 若目标文件已经存在,且比目标文件新,才会更新 # 该命令可以把多个文件一次移动到一个文件夹中,但…

github删除自己创建的仓库

1.进入仓库,点击Settings 2.下拉至Danger Zone区域,点击Delete this repository 3.点击 I want to delete this repository 4.点击i have read ... 5.按提示输入,点击Delete this repository 总结 1.进入仓库,点击Settings 2.下…

推荐9个不同风格的音频频谱波形 听音乐怎么能少了它

9个不同风格的音频频谱波形 听音乐怎么能少了它。在我们沉静在听音乐的过程中,桌面上的频谱跳动,会让音乐更有动感,视觉化把音频表现出来。在桌面上跳动的音乐,更有氛围。小小编给大家带来了9种非常有特殊的音频频谱,看…

做一个最简单的CPU -- 计算机组成原理(六)

在上一个章节中,我们已经了解了一个存储是如何制作出来的,利用这个存储我们就可以做一个最简单的CPU 指令 我们知道CPU负责执行计算机的程序,而程序其实是一个个的操作指令 比如可能是计算指令,cpu会指示ALU进行加减运算 也可…

【web网页制作】html+css旅游家乡河南主题网页制作(5页面)【附源码】

一、👨‍🎓网站题目 旅游,当地特色,历史文化,特色小吃等网站的设计与制作。 二、✍️网站描述 👨‍🎓静态网站的编写主要是用HTML DIVCSS 等来完成页面的排版设计👩‍🎓…

Leetcode面试经典-115.不同的子序列

解法都在代码里&#xff0c;不懂就留言或者私信 理论上提交这个就是最优解 class Solution {public int numDistinct(String s, String t) {if(s.length() < t.length()) {return 0;}if(s.length() t.length()) {return s.equals(t)? 1 : 0;}char[] sArr s.toCharArray…

【大模型基础】P2 Bag-of-Words 词袋模型

目录 词袋模型 概述词袋模型 实例第1步 构建语料库第2步 对句子进行分词第3步 创建词汇表第4步 转换词袋表示第5步 计算余弦相似度 词袋模型的局限性 词袋模型 概述 词袋模型&#xff0c;Bag-of-Words&#xff0c;是一种简单的文本表示方法&#xff0c;也是 NLP 中的一个经典模…

(计算机论文)基于C#、Unity的先行者技能熟练度游戏系统的设计与实现

毕业设计&#xff08;论文&#xff09; ​​​​​ ​博主可接毕设论文&#xff01;&#xff01;&#xff01; 论文题目&#xff1a;基于C#、Unity的先行者技能熟练度游戏系统的设计与实现 摘 要 随着数字娱乐产业的蓬勃发展&#xff0c;独立游戏因其创新性和独特…

5.【Java开发手册】| 异常日志

这里我就列出一些我认为应当遵守的&#xff0c;并且添加一些我自己在工作中的一些感受&#xff0c;如果照着文档抄&#xff0c;那完全变成练习打字了&#xff0c;浪费读者时间&#xff0c;如果你也认同我的看法&#xff0c;或者和我有类似感受&#xff0c;可以点个关注&#xf…

【Java 优选算法】双指针(上)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 移动零 分析 代码 复写零 分析 代码 快乐数 分析 代码 盛最多水的容器 分析 代码 移动零 题目链接 分析 双指针算法,利用两个指针cur和dest将数组划分为三个区间…

Java题集综合实践——简易计算器制作

此系列文章收录大量Java经典代码题&#xff08;也可以算是leetcode刷题指南&#xff09;&#xff0c;剩余文章指路Java题集。希望可以与大家一起探索Java的神秘。3、2、1&#xff0c;请看&#xff01; 本篇文章将带大家编写一个简易算术计算器。 功能&#xff1a;将几个算术功…

VBA经典应用69例应用6:格式化代码中的双引号(””)和井号(#)

《VBA经典应用69例》&#xff08;版权10178981&#xff09;&#xff0c;是我推出的第九套教程&#xff0c;教程是专门针对初级、中级学员在学习VBA过程中可能遇到的案例展开&#xff0c;这套教程案例众多&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以便…

使用java做一个微信机器人

如何使用Java开发微信机器人 在当今社交网络盛行的时代&#xff0c;微信已成为人们生活中不可或缺的工具。为了更好地服务用户&#xff0c;许多企业和个人开始利用微信机器人来自动化回复消息、发布信息等操作。本文将介绍如何使用Java开发一个简单的微信机器人&#xff0c;并…

html基础语法 看这一篇就够了!

HTML 一 概念 html:html 文件根标签 head:编写页面相关的属性 title:页面标题 body:页面内容展示信息 二 DOM 树&#xff1a; 所有的标签都是 html 的子标签 head 和 body 是兄弟标签&#xff0c;同一级别 head 和 title 为父子标签 1.第一个程序 <html><head>…