数据结构(邓俊辉)学习笔记】排序 6——希尔排序:框架 + 实例

news2024/9/21 19:01:11

文章目录

  • 1. 策略
  • 2.实例
  • 3.循秩访问
  • 4. 插入排序
  • 5.Shell序列

1. 策略

来学习一种非常别致的排序算法,也就是希尔排序。
在这里插入图片描述

希尔排序算法既有着悠久的历史,同时也仍然不失活力。该算法的别致之处在于,它不再是将输入视作为一个一维的序列,而是将其视作为一个二维的矩阵,并且试图对矩阵的每一列分别进行排序。

如果矩阵当前的宽度为 w,那么我就将所有这 w 列各自的排序总称为 w-sorting。实际上每一次希尔排序的过程都是由若干个宽度不同的 w-sorting 构成的。

如果矩阵的每一列都已经过排序,我们就称之为 w-ordered。实际上矩阵最开始比较宽,w 比较大,此后 Shellsort 会逐步地压缩矩阵,使之越来越高,越来越窄。每压缩一次都随机执行一趟对应的 w-sorting,从而使之变成 w-ordered。

也可以过这样一组图来说明这一过程。比如这可能就是最初的那个矩阵,相应的比较宽、比较矮。那么在执行完对应的 w-sorting 之后,Shellsort 会对这个矩阵进行重组。使之成为一个相对更窄但同时更高的矩阵。接下来对应于新的这个宽度,wk -1也会做一趟逐列的排序,而在此之后,Shellsort 又会对它进行重组,使之变成这样一个更加的窄,更加高的矩阵。

这个过程将持续地进行下去。总之矩阵会变得越来越高、越来越窄,直到最终变成只有一列。同样的,对于最后这个矩阵我们也需要来做一次对应的 w-sorting,只不过此时的 w = 1,所以我们也称之为 one-sorting。

可以看到整个 Shellsort 过程使用了一系列的宽度,也就是 wk、wk-1 以及一直到 w3、w2和 w1。这些宽度合在一起构成了所谓的步长序列 step sequence。

当然,这些矩阵宽度被使用的次序恰好与它们在序列中的次序相反。然而,无论如何,它们都必须是逐个单调递减的。没错,在算法的执行过程中,我们所采用的矩阵宽度会逐步地递减,所以希尔排序算法也称作为递减增量法。

请注意我们这里的步长序列 h, 实际上除了我们刚才所说的单调性,以及它的首相必须为1,我们对它暂时还没有更多的要求。是的,这种序列有很多种可能的选择,采用不同步长序列, Shellsort 的性能也会有所不同。

实际上 Shellsort 只是一个框架,你采用什么样的步长序列就会得到什么样的算法。从这个意义上讲,Shellsort 就像一个播放机,往里头放入什么样的 CD,它就会播放什么样的音乐。因此宁愿说 Shellsort 是一个算法,不如说它是一类算法。

我们注意到,既然任何步长序列都要求首项 w1 = 1,所以任何Shellsort 都是以 one-sorting 结束的,而任何一次这样的 one-sorting 其实也就相当于全局的排序。因此最终的输出结果也必然是正确的排序列。因此这个算法的正确性是毫无疑问的。

当然,至此你可能会有一个疑问,既然无论如何,最终都要做一次 one-sorting,那么此前的这些排序又有什么意义呢?是的,这正是 Shellsort 的奥妙所在。不过现在回答这个问题还维持尚早。接下来我们通过一个具体的实例,首先来切实地感受一下希尔排序的执行过程。

2.实例

考察这个由13个整数所构成的待排序序列。
在这里插入图片描述

采用希尔排序算法,我们首先将矩阵的宽度取做8,于是按照不超过8个元素为准则,将整个序列分为两段,而每一段都对应于矩阵的一行,这样我们就得到一个宽度为8的矩阵。

接下来我们对这个矩阵逐列排序,很容易验证第1列的排序结果是这样,第2列是这样,第3列是这样,第4列是这样,以及第五列是这样。最后3列是退化的情况,直接得到排序的结果。

至此我们已经完成了对应于宽度 8 的一趟 sorting。在转换为新的矩阵之前,我们需要将它重新复原为一个线性的序列。具体来说,与刚才构成矩阵的操作完全相反,我们这时候需要将矩阵的每一行逐个取出并依次串接,从而构成一个线性序列。
在这里插入图片描述

我们接下来采用的矩阵宽度为5,因此我们接下来要以5为单位,将子序列切分为若干段,而每一段都将作为新矩阵的一行。接下来,我们依然需要逐列排序,不难验证这是第1列的排序结果,这是第2列的排序结果,以及第3列、第4列、第5列的排序结果。

一旦完成了这样的逐列排序,我们又需要在逻辑上将这个矩阵重新恢复为一个序列。具体的方法依然与刚才构造矩阵的过程相反,也就说我们需要将矩阵中的每一行逐个取出并依次串接,并且还原为一个线性的序列。

此时我们不妨稍作停留,来观察一下这两个中间结果。虽然我们现在还不能精确地度量,但是我们依然能够隐约而且切实的感觉到整个序列的有序性是在不断地改善。那么接下来还会继续改善吗?不妨继续下去。
在这里插入图片描述

再接下来的一步,我们将矩阵的宽度取做3,也就说我们将以每三个元素为单位,将整个序列分割成若干段。这些段也就分别构成了新矩阵的各行。这个新矩阵共有三列,我们接下来依然需要对这个新矩阵中的3列分别排序,同样不难验证,各列分别排序的结果应该是这样。

接下来我们依然需要逐行取出所有的元素,并将它们依次串接起来,恢复为一个线性序列。至此,也不妨再次的大致体会一下,经过刚才的这样一趟逐列排序,整个序列的有序性又向前有所改进。

在这里插入图片描述
为了再进一步的提高整个序列的有序性,我们接下来将要采用的矩阵宽度取作2,也就是说我们要以两个元素为单位,将整个序列切分成若干段,而且同样的,每一段都构成新矩阵的一行。

此后我们又可以分别针对这两列各做一次排序,不难验证排序的结果分别是这样。接下来同样的,我们又需要将这个矩阵中的元素逐行取出,并将它们依次串接起来,恢复成为一个线性序列。

请再次的体会一下,经过这样的一趟逐列排序,整个序列的有序性又有所改善。

在这里插入图片描述

与所有的步长序列一样,我们最终也是终止于 w1 = 1。也就说我们要将此时的线性序列完整的视作为一列,并且对它进行排序,在经过了以上的各步之后,不出意外,我们的确得到了原序列的一个排序结果。

3.循秩访问

在这里插入图片描述

通过刚才的实例,相信你对希尔排序的整个过程已经有了足够的了解,那么这样一个计算过程又当如何实现呢?实际上,如果需要真正的实现这样一个完整的计算过程,我们还有一些技术细节需要讨论。

比如我们首先碰到了一个技术问题,就是矩阵的重排。难道为此我们需要真的去引入一个二维的向量结构吗?其实大可不必,如果输入序列本来就是以一维向量形式给出的,那么我们只需在这样一个一维向量上进行操作就足够了。

我们注意到,在每一步具体的迭代中,矩阵当前的宽度都可以视作为一个常数w。因此按照这幅图所示的方式,只需从逻辑上而不是物理上完成矩阵的重排就够了,因为对于任何一个特定的矩阵宽度,在这个矩阵中的任何一个元素在原序列中所对应的秩都是可以直接换算出来

准确地讲,在这个矩阵中位于第 K 行第 I 列的元素,在原序列中所对应的秩无非就是 i + kw。因此,尽管刚才以及接下来我们在讲解时都倾向于使用一个二维矩阵,但请你务必牢牢记住,在物理上,它始终都无非就是那个输入的一维向量而已。

而我们之所以能够做到这样,需要归功于向量循秩访问的特性。

4. 插入排序

在这里插入图片描述

接下来的一个技术要点是在每一趟迭代中,逐列的排序又当如何具体实现?非常有趣的是,在这里我们并不需要去追求非常高效地排序算法。事实上,我们这里在底层所采用的排序算法只需要具有输入敏感性就可以了,也就是说随着算法的不断推进,在序列的有序性不断改善的同时,这类算法单次运行的成本将会逐渐地递减,从而能够保证总体计算成本的足够低廉。

是的,我们早先介绍的某些初等排序算法的确就具有这种输入敏感性,你还记得吗?没错,插入排序 insertionsort,你应该记得我们曾经通过逆序对的数目来度量序列的无序性,而插入排序的计算成本恰恰就取决于输入序列的这一指标

当然影响希尔排序总体效率的最大因素莫过于其中具体采用的步长序列 h。接下来我们将会具体的剖析其中的几个实例。需要指出的是,在评判这些序列的相互优劣时,我们主要需要考察这样几个方面:

  1. 包括在整个算法的过程中,我们需要执行的关键码比较以及数据项移动操作的次数。
  2. 另外我们也十分关注整个过程中所涉及的迭代次数,也就是矩阵的重组次数,因为每一次矩阵的重组都对应于整个序列的一趟遍历,而在数据量非常大时,每一次遍历都有可能会引发相应的 IO,因此在这种情况下,迭代的次数也就自然成为了一个非常敏感而重要的因素。

以下我们就来考察步长序列的一个具体实例,这个序列提出得更早,因此也难免有更多的缺点。

5.Shell序列

在这里插入图片描述

我们这里结识的第一个步长序列就出自于希尔排序的发明者希尔本人之手,接下来就会看到这个序列存在很多缺点,尽管从某种意义上来看,它非常优美,因为我们注意到每一项都整齐划一的是2的 k 次方的形式。也就说每一项都是前一项的两倍,那么这序列的缺点就集中体现在它在最坏情况下可能会导致 n 平方量级的运行时间。 为此我们可以构造这样一个具体的实例:

我们首先来考察两个整数区间,也就是0~ 2 n − 1 2^{n-1} 2n1,以及 2 n − 1 2^{n-1} 2n1~ 2 n 2^{n} 2n,其中包含的整数都是 2 n − 1 2^{n-1} 2n1个,只不过在数值上前者更小,而后者更大。
  ~  
接下来我们将这两组整数分别的打乱次序,并相应地构成两个子序列,A 以及 B。然后我按照 ABAB 交错的形式,将它们汇合为一个完整的序列。比如对于 n = 4而言,这就是一个可能的生成序列。可以看到它是由两个规模都为 8 的子序列交错构成的,子序列 A 中的元素都被安置在秩为奇数的位置,而对称的子序列B中元素都被安放在秩为偶数的位置。
  ~  
现在我们假设就采用希尔序列来对它进行排序。我们考察算法的倒数第二步,也就是以 2 为间隔的那轮排序刚刚结束的时候,我们可以断言,此时序列的组成必然是这样。也就是说原先来自于子序列 A 中的那些元素依然占据着秩为奇数的位置,而且这8个元素的相对次序已经是完全有序的。
  ~  
同时对称的,原先来自于子序列 B 中的那些元素也必然仍旧占据着秩为偶数的那些位置,而且仅就这 8 个元素而言,它们之间的相对次序也已经是有序的。这两个子序列在这个时刻的有序性并不难理解,在刚刚过去的这一轮排序中,这两个子序列恰好各自就是独立成为一列,因此所谓的2-sorting, 其实就是对这两列分别进行排序,所以它们的结果自然应该是各自有序的。

当然,刚才我们所指出的另一个现象更会引发我们的好奇,也就是说,无论我们此前经历过多少趟的排序交换,来自于子序列 A 和子序列 B 中的元素始终都是分别占据着秩为奇数和偶数的位置。二者泾渭分明,没有任何的元素互换。

为此我们需要反观希尔序列,我们注意到在这个序列中,除了第一项,其余各项都是偶数。没错,偶数。这就意味着在这些项所对应的每一个重组的二维矩阵中同属一列的元素或者都来自集合 A 或者都来自于 B,自然不会发生 A 与 B 之间的元素互换了。因此直到执行完 2-sorting 之后,这两个序列必然都是井水不犯河水,互不相扰。然而这恰恰就是问题所在。

对于这样的序列,在接下来的最后一趟排序,也就是 one-sorting 中,我们必然需要付出高昂的代价,因为在这个序列中依然包含着大量的逆序对。我不妨只统计 B 中的元素所参与构成的逆序对。首先是全局最大的15,它与其后的7就构成了一个逆序对。接下来在考察次大的14,我们来看出它与6和7构成了两个逆序对。再接下来是13,它与 5 6 7总共构成了三个逆序对。以下类推,元素12将与 4 5 6 7 总共构成 4 个逆序对。

我想你已经看出其中的规律了。没错,B 中的各元素所参与构成的逆序对数恰好构成一个算数级数。没错,算数级数,我想经过这门课的学习,你现在应该有了一个直觉的反馈。是的,这样一个算数基数对应的将是平方量级的运行成本,也就是说算法的效率已经退化为与起泡排序相当了

当然在这里我们并不满足于仅仅指出希尔序列的缺点,而更重要的是,我们需要探究导致这种缺陷的根源。让我们将目光再次投回到希尔序列,我们会发现,与其说其中大量的元素都是偶数,不如更一般的说其中的各项并非互素,因此每一轮的排序都有大量的精力浪费于对前一轮排序工作的重复之上。是的,相邻项要尽可能的互素。这样我们也就拿到了打开新方法大门的钥匙

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

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

相关文章

SpringTest框架JUnit单元测试用例获取ApplicationContext实例的方法

JUnit单元测试用例中使用Spring框架,之前我的使用方式很直接。 /*** 用于需要用到Spring的测试用例基类* * author lihzh* alia OneCoder* blog http://www.coderli.com*/ RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations { "/sprin…

七. 部署YOLOv8检测器-deploy-yolov8-basic

目录 前言0. 简述1. 案例运行2. 补充说明3. 代码分析3.1 main.cpp3.2 trt_detector.hpp3.2 trt_detector.cpp 4. INT8量化前瞻总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考 本次课程…

【车载开发系列】ParaSoft入门介绍

【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍一. ParaSoft的背景二. 设计理念三. ParaSoft C/CTest简介四. 具备常用功能1)静态代码分析2)代码覆盖率分析3)模糊测试4)自…

协议的认识和理解

目录 1. 协议 1.1. 站在日常生活的角度初始协议 1.2. 网络分层结构 (OS vs 网络) 1.2.1. 软件分层 1.2.2. 网络分层 1.3. 站在语言的角度理解协议 1. 协议 对于协议,我们可以用一句话概括它:协议本质上就是一种约定。 1.1. 站在日常生活的角度初始…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(四)

概述 在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0,并为其加入了全新的 History Trace、“墓碑”等诸多激动人心的新功能。那么它们到底如何实际应用到我们的 App 中去呢? 想搞清楚历史记录追踪(History Trace)如何在 SwiftData 2.0 中大放异彩吗?看这篇就对了! …

压缩文件隐写

1、伪加密 (1)zip伪加密 考点:winhex打开压缩包;搜索504b0102(注意不是文件头部;zip文件头部伪504b0304);从50开始,往后面数第9,10个字符为加密字符,将其设置为0000即可变为无加密状…

攻防世界--->获取

做题笔记。 下载 查壳 64ida打开 main函数: 查找字符: 根据程序逻辑,创建了一个新文件并且进行了写入。 直接Linux上动调一下。 SharifCTF{b70c59275fcfa8aebf2d5911223c6589}

python安装以及访问openAI API

安装python 我是python小白,所以需要一步一步来,先安装。 一口吃不成胖子,记住。 从官网下载python,目前最新版本是3.12,但是据说稳定版3.11更好一点,所以,下载3.11,注意不要下载…

Hiredis的使用

Hiredis的使用 📸这里安利一个github仓库介绍 图片生成 Socialify 一键生成专业 GitHub 仓库简介图 一、Hiredis的安装与使用 1、下载hiredis软件包, https://github.com/redis/hiredis.git 或者使用git下载到本地 git clone https://github.com/redi…

Camtasia 2024破解版注册机包含激活码秘钥

🎬 嗨,亲爱的朋友们!今天我要给你们安利一款超级炫酷的屏幕录制和视频编辑软件——Camtasia 2024!🎉 camtasia2024绿色免费安装包winmac下载,点击链接即可保存。 https://pan.quark.cn/s/5ee0c4655701 C…

vite+vue3+typescript+elementPlus前端实现电子证书查询系统

实现背景:之前电子证书的实现是后端实现的,主要采用GD库技术,在底图上添加文字水印和图片水印实现的。这里采用前端技术实现电子证书的呈现以及点击证书下载,优点是:后端给前端传递的是一组数据,不需要传证…

假期学习-- iOS runloop了解和使用

iOS runloop的总结和理解 https://juejin.cn/post/7167497134294433829?searchId202408060922235E28560E3792F91107ED runloop的概念和数据结构 一般情况下,程序(或者说线程)在执行完毕后会立即退出或销毁;,但如果对…

体育馆智能可视化:提升场馆管理与观赛体验

利用图扑可视化技术,实时呈现体育馆内各项动态,优化场馆管理,提升观众的观赛体验。

基于人工智能的手写数字识别系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 手写数字识别是一种经典的计算机视觉任务,目标是让机器能够识别手写数字。通过人工智能技术,特别是卷积神经网…

JS_变量

二、JS的变量 JS中的变量具有如下特征 1 弱类型变量,可以统一声明成var 2 var声明的变量可以再次声明 3 变量可以使用不同的数据类型多次赋值 4 JS的语句可以以; 结尾,也可以不用;结尾 5 变量标识符严格区分大小写 6 标识符的命名规则参照JAVA 7 如果使用了 一个没有声明的变量…

JAVA基础:值传递和址传递

1 值传递和址传递 值传递 方法调用时,传递的实参是一个基本类型的数据 形参改变,实参不变 public static void doSum(int num1,int num2){} main(){doSum(10,20);int i 10 ;int j 20 ;doSum(i,j) ; } public static void t1(int num){num 20 ;Sys…

STM32CubeMX和HAL库-新建项目

目录 新建项目 选择开发板 MCU图形化配置界面总览 MCU配置 新建项目 新建项目包含选择MCU创建项目、选择开发板新建项目和交叉选择MCU新建项目三部分。 1. 选择MCU创建项目 单击主菜单项File→New Project,或Home视图上的ACCESS TO MCU SELECTOR 按钮&#xff0c…

Mac+Pycharm配置PyQt6教程

安装包 pip install PyQt6 PyQt6-tools #查看Qt版本 pip show PyQt6 pip show pyqt6-tools 配置扩展工具 QTD(界面设计) Program:/Users/wan/PycharmProjects/NewDemo/venv/lib/python3.11/site-packages/qt6_applications/Qt/bin/Designer.app Working directo…

dubbo 服务消费原理分析之服务目录

文章目录 前言一、RegistryDirectory1、DynamicDirectory2、RegistryProtocol.doCreateInvoker2、RegistryProtocol.subscribe3、ListenerRegistryWrapper.subscribe4、FailbackRegistry.subscribe5、ZookeeperRegistry.doSubscribe6、RegistryDirectory.notify7、RegistryDire…

无人叉车里程计模块专题

1.无人叉车里程计模块传感器要求 2.里程计功能需求 3.无人叉车里程计模块测试用例 4.无人叉车里程计算法方案 5.源码