希尔排序【C语言】

希尔排序

前言

在上一篇文章中我们了解了直接插入排序算法(建议先阅读),但其实这个算法还是有一定优化空间的。而它优化之后,就变成了另一个大名鼎鼎的排序算法:希尔排序。

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序的算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。


正文

记得么,我们说直接插入排序最差的情况也就是降序(我们要排升序)时的情况。那么有什么办法可以让这种情况的时间复杂度得到提升吗?

是有的,我们可以将我们要排序的数组划分为几段,在一段中进行直接插入排序,排完后再划分为大一点的段,再各自直接插入排序…这样我们的时间复杂度就会小于O(n²)。

希尔排序其实就是两步:

  1. 预排序
  2. 直接插入排序

希尔排序法的基本思想是:先选定⼀个整数(通常是gap=n/3+1),把待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进⾏排序;

然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进⾏插⼊排序;

当gap=1时,就相当于 直接插⼊排序。 它是在直接插⼊排序算法的基础上进⾏改进⽽来的,综合来说它的效率肯定是要⾼于直接插⼊排序算法的。

我们来画图理解一下这个排序过程:

现在我们先有这10个数据(n=10),让gap为5(n/2),也就是步长为5。也就是每隔5个数我们将其划分到一个组中。

9和4为一组,1和8为一组,2和6为一组,5和3为一组,7和5为一组。

我们划分了5组,每组2个数据,在每组里进行直接插入排序

(为了方便观察与直接插入排序的区别,将直接插入排序代码放在这:)

在每组里进行直接插入排序,也就是说在第一组里,让end为9,tmp为4。

按照我们之前的直接插入算法的做法,我们比较end与tmp的大小,end更大应该往后放,但是不再是放到end+1的位置了,而是放到end+gap的位置;

end再往前走也不再是减1而是减gap了;

end越界了,此时我们将tmp不再是放到end+1而是放到end+gap的位置。

剩下的4组也是同样的操作。

可以看到这样进行完,我们基本把小的数据放到前面而大的数据放到后面了。这第一步就叫做预排序。

然后我们gap/2,让gap变为2。每隔2划分为一组。

我们先尝试写这个代码。

我们仍用刚才的数据,但先让gap为3来看看。

这一步我们将end与tmp比较,满足条件后将tmp位置替换为end的值。

这一步我们让end往前走,发现end>=0不满足,跳出了循环,执行arr[end+gap]=tmp这一句。

然后我们再次进入外层循环。这时我们将i++改为i+=gap,来观察我们一组排完再排下一组的情况。

所以这时进入外层循环end来到的是第一组的第二个数据,也就是现在的9的位置,tmp就走到了第一组的第三个数据也就是8的位置。然后再次进行组内直接插入排序:

可见,这时第一组(黄色)的前三个数据5 8 9就排为有序了。又回到外层循环,i+=gap,所以end来到现在的9,tmp来到现在5的位置。

最后,我们的第一组就有序了。

可以看到我们此时的外循环已经结束了,但是我们只排了一组,也就是说我们外面要再来一层循环。

但是这些排完之后我们也只是把三组各自直接插入排序了,也就得到5 1 2 5 6 3 8 7 4 9,接近有序但仍然不是有序的。所以我们的gap还要除3,变为1,也就是变成了直接插入排序了

通过预排序,小的靠前,大的靠后,基本有序,然后再直接插入排序(gap=1),时间复杂度就比直接插入排序低很多。

然后我们会发现,由于gap要改变,代码会变成4层循环:

这样循环太多层了,我们需要优化

怎么优化呢?

我们刚才是一组一组分开去排的,一组排完了才能排下一组,现在我们不这么做,将代码改为:

将刚才第二层循环去掉,并且将剩下的for循环里的i+=gap改为i++

现在我们就变成了end从前往后逐次加一,也就是排完第一组的前两个数据后我们就先排第二组的前两个数据,然后再排第三组的前两个数据……这样走下去我们会走到第一组的第二个数据和第三个数据,这样我们也能把一组排好。总之,不再是一组一组去排。

还有一个问题,我们的gap到底取取多少呢?

我们一般用gap/3,也就是先分为3组。然后我们刚才所说,gap>1时我们是预排序,而gap=1时则为直接插入排序,也就是最后的一次排序。所以我们要让gap最后一次除3为1。假如我们现在n为6,第一次除3为2,第二次除3变为0,达不到我们要的效果。所以我们改为gap=gap/3+1,保证最后一次gap一定为1。

然后容易写错的,是我们的外层while循环条件,注意不能写为>=,否则在gap变为1后也就是理应最后一次排序后,gap/3+1还是1,又进入循环,变成了死循环

希尔排序与直接插入排序对比
//直接插入排序
void InsertSort(int* arr, int n)
{for (int i = 0; i < n-1; i++){int end = i;//end是有序区间的最后一个数据int tmp = arr[end + 1];while (end>=0){if (arr[end] > tmp){arr[end + 1] = arr[end];//往后放一位end--;}else{break;}}arr[end + 1] = tmp;}
}
//希尔排序
void ShellSort(int* arr, int n)
{int gap = n;while (gap>1)//不能是gap>=1,否则会死循环!!{gap = gap / 3;for (int i = 0; i < n - gap; i++)//这样tmp才不会越界{int end = i;//end最后一次为n-gap-1int tmp = arr[end + gap];//tmp最后一次为n-1while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

可以看到是非常相似的。有意思的是我们明明加了一个外层循环while,却让时间复杂度下降了。

测试代码:排序性能对比

在上一篇文章“直接插入排序”中我们写的10万个随机数的时间测试,我们在这里再次使用(具体代码详见上一篇文章):

从直接插入排序和希尔排序的对比我们可以直观地看到这个优化的显著效果。

希尔排序时间复杂度的分析

我们知道对于直接插入排序,时间复杂度最差情况也就是随着有序区间的增大,交换的次数也越来越多。而希尔排序我们分组,每组的交换并不多,且最后能实现(升序)小的数据基本在左边,大的数据基本在右边(也就是预排序),这样我们随着有序区间的增大,交换的次数也很少。在这种情况下再去直接插入排序,时间复杂度就好了很多。

我们先来看外层循环多少次,gap>1,gap=gap/3(忽略常数1),也就是说gap一开始为n,每次除3直到为1,那么会进行多少次呢?假设进行x次,那么 3 x = n 3^x=n 3x=n成立,所以 x = l o g 3 n x=log_3n x=log3n。我们说过底数可以忽略不计,所以外层的复杂度就为logn。

内循环有两个,我们再来分析一下。

到gap为1时不可能是最坏的情况,而是小的在左边,大的在右边,已经接近有序了,所以时间复杂度为O(n)。

而这个顶点的位置就为最差情况,我们难以计算,总之最后我们希尔排序的时间复杂度就为 O ( n 1.3 ) O(n^{1.3}) O(n1.3)

本文到此结束,感谢阅读=_=

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

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

相关文章

C语言中的浮点数存储:深入探讨

案例引入 请看下面一段代码并思考结果&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {int n 9;float* pFloat (float*)&n;printf("n的值为&#xff1a;%d\n", n);printf("*pFloat的值为&#xff1a;%f\n", *…

Java线程阻塞:原因

Java线程阻塞&#xff1a;原因 1. sleep()2. suspend() 和 resume()&#xff08;不推荐&#xff09;3. yield()4. wait() 和 notify()/notifyAll() &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 线程阻塞是一个重要的概念&#xff0c;它决…

Linux下docker部署drools并集成项目使用

Linux下docker部署drools并集成项目使用 一、背景介绍二、 思路方案三、过程四、总结 一、背景介绍 上一篇文章是对规则引擎的基本介绍&#xff0c;本篇文章是对于drools规则引擎的基本使用。 二、 思路方案 前提&#xff1a;首先保证主机联网、有docker环境、保证Linux空闲…

OS—文件系统

目录 一. 文件系统结构I/O 控制层基本文件系统文件组织模块逻辑文件系统 二. 文件系统布局文件系统在磁盘中的结构主引导记录(MasterBoot Record,MBR)引导块(boot block)超级块(super block)文件系统中空闲块的信息 文件系统在内存中的结构 三. 外存空间管理空闲表法空闲链表法…

面向对象 - 概述、类的创建、 实例化与内存解析

一、学习面向对象的三条主线 Java类及类的成员&#xff1a;&#xff08;重点&#xff09;属性、方法、构造器&#xff1b;&#xff08;熟悉&#xff09;代码块、内部类面向对象的特征&#xff1a;封装、继承、多态、&#xff08;抽象&#xff09;其他关键字的使用&#xff1a;…

北欧风情在浦东,5 大公司为你定制美好

在繁华的浦东&#xff0c;追求高品质生活的您&#xff0c;是否渴望拥有一个充满北欧风情的温馨家园&#xff1f;今天&#xff0c;我们将为您推荐 5 家顶尖的装修公司&#xff0c;它们将以精湛的工艺和独特的设计理念&#xff0c;为您量身定制梦想中的北欧风家居。 推荐一&#…

大数据-56 Kafka SpringBoot与Kafka 基础简单配置和使用

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

大厂的堡垒机到底是啥?为什么需要它?

什么是堡垒机 堡垒机&#xff0c;即在一个特定的网络环境下&#xff0c;为了保障网络和数据不受来自外部和内部用户的入侵和破坏&#xff0c;而运用各种技术手段监控和记录运维人员对网络内的服务器、网络设备、安全设备、数据库等设备的操作行为&#xff0c;以便集中报警、及…

【文件解析漏洞】实战详解!

漏洞描述&#xff1a; 文件解析漏洞是由于中间件错误的将任意格式的文件解析成网页可执行文件&#xff0c;配合文件上传漏洞进行GetShell的漏洞! IIS解析漏洞&#xff1a; IIS6.X&#xff1a; 方式一:目录解析 在网站下建立文件夹的名字为.asp/.asa 的文件夹&#xff0c;其目…

免费发送邮件两种接口方式:SMTP和邮件API

SMTP与邮件API在处理大批量邮件发送时&#xff0c;哪个更稳定&#xff1f; 在现代信息化的社会中&#xff0c;邮件已成为不可或缺的沟通工具。无论是个人还是企业&#xff0c;发送邮件都是日常工作的一部分。AokSend将详细介绍两种常用的免费发送邮件接口方式&#xff1a;SMTP…

麒麟V10系统统一认证子系统国际化

在适配麒麟V10系统统一认证子系统国际化过程中&#xff0c; 遇到了很多的问题&#xff0c;关键是麒麟官方的文档对这部分也是粗略带过&#xff0c;遇到的问题有: &#xff08;1&#xff09;xgettext无法提取C源文件中目标待翻译的字符串。 &#xff08;2&#xff09;使用msgf…

程序一调用这个接口就会崩溃, 因为他的静态库添加是放在release文件下,而我用的debug模式

程序一调用这个接口就会崩溃 因为他的静态库添加是放在release文件下 而我用的debug模式 DESTDIR ../x64/ReleaseINCLUDEPATH ./../3rdparty/ZZDecode/include LIBS -lopengl32 \-lglu32 \-luser32 \./../3rdparty/ZZDecode/x64/release/ZZDecodeInterface.lib

Python软件开发:AI毕业设计生成器引领未来

&#x1f31f; 革新软件开发&#xff1a;Python毕业设计生成器引领未来 &#x1f680; 目录 &#x1f31f; 革新软件开发&#xff1a;Python毕业设计生成器引领未来 &#x1f680;&#x1f393; 课题简介&#x1f31f; 开发目的&#x1f4c8; 开发意义 &#x1f4da; 研究方法&…

Jvm的无关性

Jvm具有无关性&#xff0c;主要体现在两个方面&#xff1a; 平台无关性&#xff1a;任何操作系统都能运行Java代码。 语言无关性&#xff1a;Jvm能运行除Java以外的其他代码。 Java源代码首先需要使用Javac编译器编译成 .class文件&#xff0c;然后由Jvm执行.class文件&…

如何准备 Java API 文档以供下游对接

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119@qq.com] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? 专栏导…

如何在 Odoo 16 Studio 中添加智能选项卡和管道

具有优雅定制功能的软件系统&#xff08;如 Odoo ERP&#xff09;可让客户调整和个性化其公司应用程序。定制在过去并不普遍&#xff0c;但现在对于组织来说&#xff0c;满足客户需求和需求激增至关重要。即使许多行业的竞争很少&#xff0c;但当前的竞争市场仍不稳定。尽管引入…

Mybatis批量更新数据库错误

问题&#xff1a;记录一次使用Mybatis批量更新数据库的错误&#xff0c;错误信息&#xff0c;Error updating database. Cause: org.postgresql.util.PSQLException: 错误: 字段 "update_time" 的类型为 timestamp without time zone, 但表达式的类型为 text 建议&am…

Prometheus+Grafana 监控平台实践-搭建常用服务监控告警

前言 Prometheus 是一个开放性的监控解决方案,通过各种 Exporter 采集当前主机/服务的数据,和 Grafana 相结合可以实现强大的监控和可视化功能 本篇将分享使用 docker compose 构建 Prometheus+Grafana,并监控之前文章所搭建的主机&服务,分享日常使用的一些使用经验 文…

7月速览| 卓翼飞思获荣誉、助大赛、展技术!

行业殊荣 ● 荣获 “全国低空经济先导产业行业产教融合共同体” 常务理事单位称号&#xff0c;助力打造低空经济产业领域人才智库。 “共同体”是低空经济领域&#xff0c;国家职教战略与新质生产力发展战略融合对接的重要成果。旨在汇聚优质资源&#xff0c;搭建交流平台&…

传统放牧方式与北斗科技的碰撞:北三短报文头羊定位追踪器PD28守护放牧生活

在大草原的广袤天地中&#xff0c;放牧生活是蒙古族人民的传统之一。然而&#xff0c;除了美丽和自由&#xff0c;放牧生活也伴随着一些危险。以前由于科技落后&#xff0c;人工成本低&#xff0c;主要依靠人力去放牧&#xff0c;牧民放牧顶风踏雪走个几十公里都极为寻常。除了…