【数据结构与算法】直接插入排序和希尔排序

引言

进入了初阶数据结构的一个新的主题——排序。所谓排序,就是一串记录,按照其中的某几个或某些关键字的大小(一定的规则)递增或递减排列起来的操作

排序的稳定性:在一定的规则下,两个值相等的元素,在排序算法处理前后的相对位置是否发生变化,如果相对位置变化,称这种排序算法是稳定的,否则为不稳定的。(这个概念并不影响你对排序的学习)

排序将会是初阶数据结构的收尾模块,在这个模块中,将会带领大家学习七大知名的排序方式。而在本篇博客中,将会介绍其中的两个排序,一个是直接插入排序,另一个则是希尔排序。不过在开始我们排序的讲解之前,先介绍一下我们将要讲的排序算法都有哪些。

没错,我们今天将要处理的就是插入排序模块。

直接插入排序

直接插入排序是一种简单的插入排序法,如果想更好的理解希尔排序,首先需要弄懂直接插入排序,其基本思想是:

把待排序的元素按其大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

很像我们打扑克时,别人给你发一副乱序的牌让你自己手动排序的过程(需要从左往右依次排好顺序):

直接插入排序核心逻辑:当你插入第 i 个元素时,前面的 array[0],array[1]……array[i-1] 已经排好序,此时让array[i]的数据按array[i-1]……往前的顺序依次比较,找到可插入的位置插入array[i]。原来位置上的元素顺序后移。

这里博主找了一个动图供大家参考理解:

实现直接插入排序

我们可以先来分析一下单趟的直接插入排序,分为两种情况:

1. 单趟排序(单独取一趟排序分析其过程)过程中end走到序列中间即插入,此时tmp小于a[end]

 2. [0,end] 区间中元素均小于需插入元素 a[end+1] ,也就是tmp时,单趟排序end走到序列最前面,end == -1

下面是单趟的代码实现:

int end = 3;
int tmp = a[end + 1];
while (end >= 0) {if (tmp < a[end]) {a[end + 1] = a[end];--end;}else break;
}
a[end + 1] = tmp;

当end的值从1一直排到末尾序列值n - 2时,整个插入排序便完成了。

for循环遍历 end \epsilon [0, n - 2] ,由于长度为n的数组有效下标最大为n - 1,当end为n - 1时,要插入的元素tmp存的刚好就是a[end + 1],也就是下标为n - 1的数,同时也就是数组的最后一个值。

直接插入排序代码

// 直接插入排序
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++) {// [0,end]区间内有序,end + 1位置是待插入元素int end = i;// tmp保存的是待插入元素的值int tmp = a[end + 1];while (end >= 0) {if (tmp < a[end]) {// 后移元素操作a[end + 1] = a[end];--end;}else break;}//元素的插入a[end + 1] = tmp;}
}

直接插入排序的特性

  1. 时间复杂度:O(N^2)
  2. 空间复杂的:O(1)
  3. 元素越接近有序,直接插入排序算法效率越高。
  4. 稳定性:稳定
  5. 最好情况:有序/接近有序
  6. 最坏境况:逆序

当一个序列接近有序时,每一趟直接插入的过程都会变得简单很多,即往前走上几步便能找到比tmp大的值从而跳出单趟循环,在每一次循环的跳出过程中,直接插入排序的时间复杂度可达 O(N)。相反,当一个排序按逆序排列,每一趟都需要将前面的所有元素往后移动一次插入tmp,时间复杂度便成了计算一个等差数列和:

1+2+3...+(n-1)=\frac{n(n-1)}{2},其时间复杂度显而易见——O(N^2)。

希尔排序

希尔排序也是插入排序的一种,是一个名叫希尔(Donald Shell)的大佬思考推论得出的排序方式,其底层逻辑本质上来说还是直接插入排序。希尔大佬发现了直接插入排序元素越接近有序,直接插入排序算法效率越高这一特点,突发奇想:直接插入排序在非顺序的元素序列中,如果插入元素的值较小,需要从插入尾部一步步移到头部,这个过程中的消耗无疑是巨大的。如果能有一种方式,能将乱序元素序列通过允许远距离的交换元素进行预排列,快速生成一个接近有序的序列,这时候在调用直接插入排序,排序的速率是否会大大提升。

在希尔排序正真被众人所接受之前,这个排序方式也备受质疑,但时间总会给出答案,希尔排序在现今排序大家庭中有着举足轻重的地位。

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序序列中所有元素分成 i 个组所有距离为 i 的记录分在同一组内,并对每一组内的元素进行直接插入排序。然后,取 i = n / 2 (n为排序序列元素个数) 重复上述分组和排序的工作。当到达 i = 1 时,所有记录在统一组内排好序

总结一下其过程:

  1. 预排序(gap > 1)
  2. 直接插入排序(gap == 1)

实现希尔排序

我们可以首先来分析一下单趟的希尔排序。

设 gap == 3 的时候:

每隔3个元素取一个数,最后可以分成gap(gap == 3)组数

这时候,将分好的每一组进行排序,排序方式为直接插入排序。注意,在排序的过程中,不同的组之间的位置不会有交集,元素的位置始终实在自己组内变动的。拿上面举例,gap == 3的第一组数只会在0,3,6,9位置上排序,不会将数字排到其他组的位置上。

这里我们可以复用一下之前选择排序的单趟,只不过++变成了+=gap,--变成了-=gap(因为是按照gap分组间隔排序的,不同组需要排序的元素之间间隔都为gap),下面是排单组(第一组:4 8 3 7)元素时的代码。

int gap = 3;
for (int i = 0; i < n - gap; i += gap) {int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;
}

这时候,想要排分好的多组元素就会容易非常多了,我们再套一层循环,就可以达到排序不同组的效果

gap = 3;
for (int rem = 0; rem < gap; rem++) {for (int i = 0; i < n - gap; i += gap) {int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;}
}

这里的代码,就成功的达到帮gap的所有组排序的效果了。现在,实现希尔排序就只差最后一步,就是改变gap的值,让其从n / 2,一直除2直到除到1为止每得到一次gap都进行一次上面的分组排序运算,下面就是完整的希尔排序代码。

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1) {gap = gap / 2;for (int rem = 0; rem < gap; rem++) {for (int i = 0; i < n - gap; i += gap) {int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;}}}
}

 希尔排序代码

不过,你难道以为希尔排序到这里就结束了?其实这份代码还有优化的空间。比如说,其实你可以省去遍历不同组的for循环,像下面这样。其实本质没什么变化,就是把一组一组拿出来排序的方式改成按顺序在不同组之间换着排。

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1) {gap = gap / 2;//去掉遍历不同组的for循环,下面遍历的i+=gap改为i++for (int i = 0; i < n - gap; i++) { int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;}}
}

你还可以将gap = gap / 2 改成gap = gap / 3 + 1

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1) {gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++) {int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;}}
}

关于希尔排序的一些特性和问题

不过对于gap的取法其实很多,最初Shell提出gap = gap / 2,后来Knuth提出取 gap = (gap / 3) + 1,还有人提出取奇数或者是互质更好。但无论哪一种主张都没有得到证明

《数据结构-用面相对象方法与C++描述》--- 殷人昆

希尔排序的特性

  1. 时间复杂度:希尔排序的时间复杂度不好计算,gap的取值方法很多,导致很难去计算,在好些书中给出的希尔排序的时间复杂度都不固定。Knuth经过大量的实验统计,复杂度大概在O(n^1.25)~O(1.6*n^1.25)之间。现代更高效的增量序列可以使希尔排序达到O(N*logN)的复杂度。
  2. 希尔排序是对直接插入排序的优化。
  3. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。整体而言,可以达到优化的效果
  4. 稳定性:不稳定

《数据结构(C语言版)》--- 严蔚敏

测试计算效果

clock()函数是<time.h>头文件中的一个函数,用来返回程序启动到函数调用之间的CPU时钟周期数。这个主可以用来帮助我们衡量程序或程序某个部分的性能。

我们可以计算对比一下本篇博客两个排序方式占用的CPU时间

void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);}

上面这段代码的功能是生成十万个随机数,分别用希尔排序直接插入排序去排列,同时用clock记录所消耗的时间,打印结果。

我们可以发现希尔排序相比于直接插入排序性能提升了很多

结语

本篇博客的内容到这里就结束了,插入排序的序列元素越接近有序,直接插入排序算法效率越高。希尔正是发现了其特点,引入“增量”的概念,允许排序中远距离的交换元素,快速达到预排序效果,大幅度提高了对大规模数据集的排序效率。直接插入排序和希尔排序在计算机科学的排序算法领域中占有重要地位。在掌握其中规律之后,相信你对排序一定有了更加深入的理解。

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

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

相关文章

基于springboot实现校园周边美食探索及分享平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现园周边美食探索及分享平台系统演示 摘要 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的…

【漏洞复现】chatgpt pictureproxy.php SSRF漏洞(CVE-2024-27564)

0x01 漏洞概述 ChatGPT pictureproxy.php接口存在服务器端请求伪造 漏洞&#xff08;SSRF&#xff09; &#xff0c;未授权的攻击者可以通过将构建的 URL 注入 url参数来强制应用程序发出任意请求。 0x02 测绘语句 fofa: icon_hash"-1999760920" 0x03 漏洞复现 G…

OSCP靶场--Codo

OSCP靶场–Codo 考点 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.229.23 -Pn -sV -sC --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-25 05:04 EDT Nmap scan report for 192.168.229.23 Host is up (0.35s latency). Not sh…

基于龙芯2k1000 mips架构ddr调试心得(二)

1、内存控制器概述 龙芯处理器内部集成的内存控制器的设计遵守 DDR2/3 SDRAM 的行业标准&#xff08;JESD79-2 和 JESD79-3&#xff09;。在龙芯处理器中&#xff0c;所实现的所有内存读/写操作都遵守 JESD79-2B 及 JESD79-3 的规定。龙芯处理器支持最大 4 个 CS&#xff08;由…

基于Hive的天气情况大数据分析系统(通过hive进行大数据分析将分析的数据通过sqoop导入到mysql,通过Django基于mysql的数据做可视化)

基于Hive的天气情况大数据分析系统&#xff08;通过hive进行大数据分析将分析的数据通过sqoop导入到mysql&#xff0c;通过Django基于mysql的数据做可视化&#xff09; Hive介绍&#xff1a; Hive是建立在Hadoop之上的数据仓库基础架构&#xff0c;它提供了类似于SQL的语言&…

让IIS支持.NET Web Api PUT和DELETE请求

前言 有很长一段时间没有使用过IIS来托管应用了&#xff0c;今天用IIS来托管一个比较老的.NET Fx4.6的项目。发布到线上后居然一直调用不同本地却一直是正常的&#xff0c;关键是POST和GET请求都是正常的&#xff0c;只有PUT和DELETE请求是有问题的。经过一番思考忽然想起来了I…

Spring Cloud+Spring Alibaba笔记

Spring CloudSpring Alibaba 文章目录 Spring CloudSpring AlibabaNacos服务发现配置中心 OpenFeign超时机制开启httpclient5重试机制开启日志 SeataSentinel流量控制熔断降级热点控制规则持久化集成 OpenFeign集成 Gateway MicrometerZipKinGateway路由断言过滤器 Nacos 服务…

Spring用到了哪些设计模式?

目录 Spring 框架中⽤到了哪些设计模式&#xff1f;工厂模式单例模式1.饿汉式&#xff0c;线程安全2.懒汉式&#xff0c;线程不安全3.懒汉式&#xff0c;线程安全4.双重检查锁&#xff08;DCL&#xff0c; 即 double-checked locking&#xff09;5.静态内部类6.枚举单例 代理模…

C++超市商品管理系统

一、简要介绍 1.本项目为面向对象程序设计的大作业&#xff0c;基于Qt creator进行开发&#xff0c;Qt框架版本6.4.1&#xff0c;编译环境MINGW 11.2.0。 2.项目结构简介&#xff1a;关于系统逻辑部分的代码的头文件在head文件夹中&#xff0c;源文件在s文件夹中。与图形界面…

算法---动态规划练习-6(地下城游戏)

地下城游戏 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 首先&#xff0c;定义一个二维数组 dp&#xff0c;其中 dp[i][j] 表示从位置 (i, j) 开始到达终点时的最低健康点数。 初始化数组 dp 的边界条件&#xff1a; 对…

基于Echarts的超市销售可视化分析系统(数据+程序+论文)

本论文旨在研究Python技术和ECharts可视化技术在超市销售数据分析系统中的应用。本系统通过对超市销售数据进行分析和可视化展示&#xff0c;帮助决策层更好地了解销售情况和趋势&#xff0c;进而做出更有针对性的决策。本系统主要包括数据处理、数据可视化和系统测试三个模块。…

【前端】Web API

1.Web API 简介 JS分为三大部分&#xff1a; ESCMScript&#xff1a;基础语法部分DOM API&#xff1a;操作页面结构BOM API&#xff1a;操作浏览器 Web API包含 DOM BOM 2.DOM基本概念 DOM全称 Document Object Mod…

【JAVAEE学习】探究Java中多线程的使用和重点及考点

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

IDEA2021.1.2破解无限试用30天破解IDEA

安装包下载 IDEA安装包&#xff1a;Other Versions - IntelliJ IDEA破解包下载&#xff1a;文件 密码:c033 开始激活 IDEA 2021.1.3 运行, 中间会先弹出一个注册框&#xff0c;我们勾选 Evaluate for free, 点击 Evaluate&#xff0c; 先试用30天: 注意&#xff0c;如果没有…

vue3封装Element表格自适应

表格高度自适应 分页跟随表格之后 1. 满屏时出现滚动条 2. 不满屏时不显示滚动条 坑 表格设置maxHeight后不出现滚动条 解决方案 表格外层元素设置max-height el-table–fit 设置高度100% .table-box {max-height: calc(100% - 120px); } .el-table--fit {height: 100%; }示例代…

李宏毅【生成式AI导论 2024】第6讲 大型语言模型修炼_第一阶段_ 自我学习累积实力

背景知识:机器怎么学会做文字接龙 详见:https://blog.csdn.net/qq_26557761/article/details/136986922?spm=1001.2014.3001.5501 在语言模型的修炼中,我们需要训练资料来找出数十亿个未知参数,这个过程叫做训练或学习。找到参数后,我们可以使用函数来进行文字接龙,拿…

Acer宏碁暗影骑士擎AN515-58笔记本电脑工厂模式原厂Win11系统ISO镜像安装包下载

宏基AN515-58原装出厂OEM预装Windows11系统工厂包&#xff0c;恢复出厂时开箱状态一模一样&#xff0c;带恢复还原功能 链接&#xff1a;https://pan.baidu.com/s/1iCVSYtList-hPqbyTyaRqQ?pwdt2gw 提取码&#xff1a;t2gw 宏基原装系统自带所有驱动、NITROSENSE风扇键盘灯…

基于TensorFlow的花卉识别(算能杯)%%%

Anaconda Prompt 激活 TensorFlow CPU版本 conda activate tensorflow_cpu //配合PyCharm环境 直接使用TensorFlow1.数据分析 此次设计的主题为花卉识别&#xff0c;数据为TensorFlow的官方数据集flower_photos&#xff0c;包括5种花卉&#xff08;雏菊、蒲公英、玫瑰、向日葵…

Python拆分PDF、Python合并PDF

WPS能拆分合并&#xff0c;但却是要输入编辑密码&#xff0c;我没有。故写了个脚本来做拆分&#xff0c;顺便附上合并的代码。 代码如下&#xff08;extract.py) #!/usr/bin/env python """PDF拆分脚本(需要Python3.10)Usage::$ python extract.py <pdf-fil…