希尔排序原理

目录:

一、希尔排序与插入排序

        1)希尔排序的概念

        2)插入排序实现   

二、希尔排序实现


一、希尔排序与插入排序

        1)希尔排序的概念

        希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

        希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。


        2)插入排序实现   

        既然希尔排序是插入排序的优化,那么我们有必要先了解一下插入排序的过程,基本操作是将需要进行排序的元素插入到已排序区当中,这样每次插入都会使已排序区长度加一。

        直观的看,插入排序的操作就和我们在打扑克牌时一样,我们默认将小的或者大的往一边插进去,插入排序也是如此。

1c2b473432304a32920ea74a72f1be53.png

         1、我们从第二个元素开始插入排序,因为这样左边只有一个数,必然有序,我们把左边的称为已排序区,右边的称为待排序区。6647501df89a4c9a8d21dcf16e5c1aaa.png

        2、将待排序区的第一个元素向已排序区插入,将其与已排序区元素从后向前比较,将其插入到合适位置,已排序区元素个数+1。c3cd3e2609aa4a34965dd9cfed8ea7b0.png

        3、然后待排序区重复2的步骤向已排序区从后往前比较,找到合适位置插入。db4dbb00f27244d7b01b8e3e8f783e6b.png

        4、 继续将待排序区元素插入到已排序区,当待排序区元素为0时,这组数据就已经排序完成。f98477b2089744a983b2657730860a50.png

        我们明白了插入排序的过程,接下来就是实现插入排序了,我们先来分析,插入排序中第一个元素(0位置处)本来就是有序的,所以我们直接从第二个元素开始操作(1位置处)。

        1、定义待排序区的首元素下标为end,用tmp记录下end下标的元素,将tmp与已排序区元素进行比较,发现小于5,则将待排序区的元素插入到首元素位置。35b026d282ec49f996a1f48fd7e21287.png

        2、已排序区数组元素加一,待排序区首元素变为3,end也变为3的下标,tmp记录此元素的值,将tmp与已排序区元素进行比较,首先与5比较,小于5。1491de6a2ad640fdb11536b6d62b4e85.png

         3、再跟1比较发现大于1,那么这个值就插入在1和5之间,已排序元素加一,待排序数组元素减一。55c58285e1e34f2f882ac6f2f396d4b2.png

        4、一直刷新end与tmp值,与已排序区进行从右往左的比较,比较到合适的位置才进行插入,而不是每次比较都插入元素。0f4e93fd82c64113a4289082b770f5d0.png

        时间复杂度最坏情况下为 O(N^2),此时待排序列为逆序或者说接近逆序最好情况下为 O(N)此时待排序列为升序,或者说接近升序。平均为O(N^2)

        空间复杂度:没有额外使用空间,所以空间复杂度为 O(1)

        代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>void InsertSort(int *a, int len)//插入排序
{int i = 0;for(i = 1 ; i < len ; i++)//从下标为1的位置进行插入排序{int end = i;//用end记录待排序区的首元素下标int tmp = a[end];//用tmp记录待排序区首元素的值while(end > 0)//保证不越界tmp就一直往前进行比较,找到合适的位置break{if(a[end - 1] > tmp){a[end] = a[end - 1];end--;}else{break;}}a[end] = tmp;//最后在将tmp值放在end的下标下}
}void Print(int *a, int len)//打印数组元素
{int i = 0;for(i = 0 ; i < len ; i++){printf("%3d",a[i]);}printf("\n");return;
}void Test()//测试
{int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };int len = sizeof(a) / sizeof(int);InsertSort(a, len);Print(a, len);return;
}int main()
{Test(); return 0;
}

运行结果:aabef0d88d164a2b9b1309fff5ec70e7.png


二、希尔排序实现

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

        注希尔排序缩小增量在数学上是个难题,大家经常用的就是gap/2。

         我们有这样一个数组:a[] = {6, 1, 5, 2, 4, 8, 3, 7, 9}。我们对这个数组进行排序,首先假设设置gap的值为3,那么这组数就会分为三组:

7bfc78fa698c43228201904e603b1f02.png

        接下来控制这三组,每组分别进行插入排序,结果为:

22ea28e204674ab0914dfb613c0998d7.png

        那么gap为3时的所有组已经排完了,接下来就该缩小增量了,gap /= 2,gap == 1:

e6c59738ee3b4844a6cee43b4e5760d2.png

        知晓了希尔排序是如何进行数据管理的,下面来看看具体的操作是如何完成的:

        1、首先, 我们需要对gap进行控制,在gap>0范围内,每次分组后的所有组排完序之后都要除以二,可以用while循环来控制gap的大小:

void ShellSort(int *a, int n)
{assert(a);int gap = n;int i = 0;while(gap > 1){if(gap > 1)gap /= 2;//..分完组后的预排序	    	}
}

        2、我们已经将缩小增量设置好了,接下来只需要把每次分完组都进行排序,也就是预排序。如何进行预排序呢?既然希尔排序是插入排序的优化,我们不妨以插排的思路对希尔预排序进行调整。

        用for循环对所有数据进行预排序,值得注意的是这里不会像插排那样循环到n,我们只需要限制在n - gap 的范围就行了,例如上图:

4815a50643dd4566bfce4fea821d9eea.png

        这个数组从3往后就不需要排了,因为在每一组的排序中最后一个值都是被拍过序的,没必要再次进行一次排序,总共为n个数据,那么就是只需要n - gap - 1个数据进行排序。则:

void ShellSort(int *a, int n)
{assert(a);int gap = n;int i = 0;while(gap > 1){if(gap > 1)gap /= 2;for(i = 0 ; i < n - gap ; i++)//控制n - gap数据进行预排序{//具体排序过程...}}
}

         3、其实预排序的实现和直接插入排序的过程几乎是完全相似,前面也说了当希尔排序的缩小增量为1时,和插入排序没区别,也就是说,插入排序每次都对相邻的数据处理,而希尔排序是将分好的组看成新的数组,例如上面数据的6, 2, 3为一组,我们可以看成其他的数据不存在,只有这一组存在,那么对于这一组而言,希尔排序就是插入排序,将上图的三组都排完序,这一趟预排序就算完成了。

        与插入排序相同,定义一个end记录当前元素下标,定义一个tmp记录a[end + gap]处的值,为什么不是a[end]处的值?可别忘了第一个值是默认有序的,所以要从第二个值向前比较,当end对应的值要大于tmp那么就将end处的值赋给下一个位置,也就是end+gap处,当不满足end处的值大于end+gap时,代表前面已经没有比自己大的值了,直接break,最后在循环结束的时候记得将a[end + gap]之前被覆盖的地方重新赋值:

void ShellSort(int *a, int n)
{assert(a);int gap = n;int i = 0;while(gap > 1){if(gap > 1)gap /= 2;for( i = 0; i < n - gap; i++)//对n组数据进行n - gap次预排序{int end = i;int tmp = a[gap + end];while(end >= 0)//当end >= 0时候对每组进行预排序{if(tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}return;
}

        这样希尔排序就完成了,其实在希尔排序的过程中,或许你还有疑问,为什么for循环这里是连续的?不是进行分组了吗?其实你仔细想想, 我们还是以上面gap==3为例,首先是第一个数据,就是对第一组的首个数据进行排序,当到了第二个数据的时候,就是对第二组首个数据进行排序,但是因为有gap的控制,这两组数据其实是互不影响的,所以连续的遍历数据进行预排序也是没有问题的。

        总结希尔排序的特性:

        1、希尔排序是对直接插入排序的优化。 

        2、当gap > 1时,都是预排序,目的是让数组更接近有序,当gap==1时,将前面预排序的结果进行直接插入排序而完成排序。

        时间复杂度O(NlogN)(近似),因为增量问题并不能准确得出时间复杂度。

        空间复杂度:没有开额外的空间,所以空间复杂度为O(1)

以下是希尔排序的完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>void ShellSort(int *a, int n)
{assert(a);int gap = n;int i = 0;while(gap > 1){if(gap > 1)gap /= 2;for( i = 0; i < n - gap; i++)//对n组数据进行n - gap次预排序{int end = i;int tmp = a[gap + end];while(end >= 0)//当end >= 0时候对每组进行预排序{if(tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}return;
}void Print(int *a, int n)
{assert(a);int i = 0;for(i = 0 ; i < n ; i++){printf("%d ",a[i]); }printf("\n");return;
}int main()
{int a[] = {5,6,1,2,7,4,8,3,9};int len = sizeof(a) / sizeof(int);ShellSort(a, len);Print(a, len);return 0;
}

cd9db51af70849ca99d062d64aad2465.png


7e524b0b6e594f19b2a43a6072cb203e.jpeg

如果这篇文章对你有帮助的话,还望各位佬能多多三连~~[doge][玫瑰] 

 

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

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

相关文章

ElasticSearch7.x - HTTP 操作 - 索引操作

创建索引 对比关系型数据库,创建索引就等同于创建数据库 在 Postman 中,向 ES 服务器发 PUT 请求 :http://192.168.254.101:9200/shopping 说明 {"acknowledged"【响应结果】: true, # true 操作成功"shards_acknowledged"【分片结果】: true, # 分片操…

紧急事件,停电导致安森美韩国厂全线停工 | 百能云芯

11月9日的消息报道&#xff0c;全球半导体公司安森美遭遇了一场意外的停电事故&#xff0c;发生在位于韩国的富川晶圆厂&#xff0c;导致整个工厂陷入停工状态&#xff01; 停电事件发生在11月5日&#xff0c;导致富川晶圆厂的产线暂时停工&#xff0c;停电持续了大约20分钟。由…

Mac电脑Visio文件编辑查看软件推荐Visio Viewer for Mac

mac版Visio Viewer功能特色 在Mac OS X上查看Visio绘图和图表 在Mac OS X上轻松查看MS Visio文件 在Mac上快速方便地打开并阅读Visio文件&#xff08;.vsd&#xff0c;.vsdx&#xff09;。 支持通过放大&#xff0c;缩小&#xff0c;旋转&#xff0c;文本选择和复制&#xff0…

DSP开发例程(4): logbuf_print_to_uart

目录 DSP开发例程: logbuf_print_to_uart新建工程源码编辑app.cfgos.cmain.c 调试说明 DSP开发例程: logbuf_print_to_uart SYS/BIOS 提供了 xdc.runtime.Log, xdc.runtime.LoggerBuf 和 xdc.runtime.LoggerSys 这几个模块用于日志记录. 日志信息在 应用程序调试和状态监控中非…

第二十九章 目标检测中的测试模型评价指标(车道线感知)

前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 介绍 自动驾驶的一大前提是保证人的安全…

中国智能驾驶的“突围赛”打响,这家本土厂商为何能成为“先行者”?

中国本土厂商正在成为全球智能汽车产业链的“核心力量”。 根据《高工智能汽车研究院》数据显示&#xff0c;今年1-6月&#xff0c;自主品牌标配L2&#xff08;含L2&#xff09;级辅助驾驶交付新车155.34万辆。其中&#xff0c;搭载中国本土智能驾驶解决方案提供商&#xff08…

Nacos使用指南

Nacos使用指南 1.认识Nacos Nacos是SpringCloudAlibaba的一个组件&#xff0c;遵循SpringCloud规范 2.Nacos的优势 1.支持服务端主动检测服务提供者状态。临时实例采用心跳检测&#xff0c;非临时实例采用主动检测 2.Nacos支持服务列表变更消息推送&#xff0c;消息更加及…

C++入门篇3(类和对象【重点】)

文章目录 C入门篇3&#xff08;类和对象【重点】&#xff09;1、面向过程和面向对象2、类的引入3、类的定义4、类的访问限定符及封装4.1、访问限定符4.2、封装 5、类的作用域6、类的实例化&#xff08;对象&#xff09;7、类对象模型7.1、类对象的存储方式7.2、结构体&#xff…

mysql索引下推

文章目录 什么是索引下推索引下推优化的原理索引下推的具体实践没有使用ICP使用ICP 总结索引下推使用条件相关系统参数 什么是索引下推 索引下推(Index Condition Pushdown&#xff0c;简称ICP)&#xff0c;是MySQL5.6版本的新特性&#xff0c;它能减少回表查询次数&#xff0…

【Proteus仿真】【51单片机】汽车尾灯控制设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用按键、LED模块等。 主要功能&#xff1a; 系统运行后&#xff0c;系统运行后&#xff0c;系统开始运行&#xff0c;K1键控制左转向灯&#xff1b;…

C# .NET Core API 注入Swagger

C# .NET Core API 注入Swagger 环境 Windows 10Visual Studio 2019(2017就有可以集中发布到publish目录的功能了吧)C#.NET Core 可跨平台发布代码,超级奈斯NuGet 套件管理dll将方法封装(据说可以提高效率,就像是我们用的dll那种感觉)Swagger 让接口可视化编写时间2020-12-09 …

MySQL on duplicate key update用法

基本使用方法 public static final String SQL_TQI_SINK "insert into " ConfigureContext.get(ConfigKeyConstants.MYSQL_TABLE_TQI) " \n" "(mile_km, mile_start_km, mile_start_m, is_out, tqi_alig_l, \n" "tqi_alig_r, tqi_surf_l…

c语言刷题第10周(16~20)

规律&#xff1a; 若多个次数最多按ASCII码顺序输出。 用for循环i取&#xff08;0~26&#xff09; 则输出满足条件的字符串中位置最靠前的那个。 用while循环遍历&#xff08;while&#xff08;a[i]!\0&#xff09;&#xff09; 从键盘输入任意只含有大小写字母的串s&…

22款奔驰S400L升级原厂360全景影像 打破死角

本次星骏汇小许介绍的是22款奔驰S400L升级原厂360全景影像&#xff0c;上帝视角看清车辆周围环境&#xff0c;更轻松驾驶 升级360全景影像系统共有前后左右4个摄像头&#xff0c;分别在车头&#xff0c;车尾&#xff0c;以及两边反光镜下各一个&#xff0c;分别用来采集车头&a…

手写C++ 实现链表的反转、删除、合并

目录 一、手写List成员方法 1.1 打印链表 1.2 删除链表节点 1.3 链表中倒数第k个节点 1.4 反转链表 1.5 合并两个排序链表 二、完整代码 一、C实现链表成员方法 在上一篇博客《手写链表C》&#xff0c;实现了基本的List类。在面试中&#xff0c;经常被问到List如何反转、…

视频集中存储EasyCVR平台播放一段时间后出现黑屏是什么原因?该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

jsvascript使用dhtmlXTreeObject的loadJSONObject绘制目录树

文章目录 1&#xff0c;引入dhtmlXTreeObject的css和js文件2&#xff0c;创建一棵目录树2.1&#xff0c;let tree new dhtmlXTreeObject(id-dhtmltree-0, "100%", "100%", 0);2.2&#xff0c;设置图片根目录&#xff08;后续使用到的图片都是相对于该目录…

JavaEE初阶学习:JVM(八股文)

1.JVM 中的内存区域划分 JVM 其实是一个Java进程~ java 进程会从操作系统这里申请一大块内存区域,给java代码使用~ 内存区域进一步划分,给出不同的用途 1.堆 new 出来的对象 (成员变量) 2.栈 维护方法之间的调用关系 (局部变量) 3.方法区(旧) / 元数据区 (新) 放的是类加载之…

Python语法基础(变量 注释 数据类型 输入与输出 运算符 缩进)

目录 变量变量命名规则变量的类型变量的创建变量的作用域 注释的方法数据类型对象和引用的概念Number(数字)数据转换 输入与输出输入函数输出函数输出函数的end参数输出格式多行语句 运算符算术运算符赋值运算符三目运算符运算符的优先级 缩进缩进格式注意事项层级嵌套 变量 标…

【计算机网络】HTTPS

文章目录 前言为什么会出现 HTTPSHTTPS 是如何进行加密的1. 对称加密非对称加密中间人攻击3. 引入证书 前言 前面我们学习了应用层中使用比较常见的 HTTP 协议&#xff0c;但是呢&#xff1f;在实际的使用中&#xff0c;浏览器和服务器之间的通信其实很少使用到 HTTP&#xff…