【数据结构初阶】希尔排序

鼠鼠最近学习了希尔排序,做个笔记!

希尔排序也是插入排序的一种捏!本篇博客也是用排升序来举例捏!

希尔排序是基于直接插入排序的,是由大佬D.L.Shell提出的。

目录

1.希尔排序

1.1.预排序

1.2.直接插入排序

2.希尔排序的时间复杂度

3.希尔排序和直接插入排序的性能比较


1.希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。如果不好懂没关系,继续看下面讲解:

鼠鼠上一篇博客介绍了直接插入排序应用时待排序的数组越接近有序,直接插入排序算法的时间效率越高。基于这个,D.L.Shell将希尔排序分为预排序和直接插入排序!

1.1.预排序

对待需排序的乱序数组,D.LShell不直接使用直接插入排序。他先搞一搞预排序。

预排序:

首先需排序的乱序数组分成gap组(若干组)。举个栗子吧:

让所有距离为gap的数分为一组,如图举例分为了gap=3组:蓝色组、红色组和绿色组。 

然后分别将蓝色组、红色组和绿色组的数据进行直接插入排序,这样每组数据排列都有序了,如图:

预排序其实大有作用,它能让待排序的乱序数组中大的数据尽量往后靠,让小的数据尽量往前靠,这样的话待排序的乱序数组就更接近有序。

那么我们来看看代码的推理过程:

1.我们先搞定蓝色组的直接插入排序的“单趟”:

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

 2.同样我们用循环控制好end就搞定了蓝色组的直接插入排序:

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

3.但是我们有gap组需要直接插入排序,那么我们再套一层循环循环gap次让不同组直接插入排序即可搞定预排序:

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

这种写法是让同一组直接插入排好再排下一组。

 鼠鼠下面再展示一种写法,其实与上面写法本质是一样的:

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

这种写法其实是多组并列插入排序 ,老爷们体会一下,鼠鼠很难解释捏!

预排序的代码我们暂且写到这里,我们可以看到:gap越大,大的数据越快往后靠,小的数据越快往前靠;但是需排序乱序数组整体越不接近有序。gap越小,则相反;当gap小到等于1时,就是直接插入排序,能让需排序乱序数组直接变成有序的。

1.2.直接插入排序

我们经过一次预排序是不是直接就让需排序乱序数组来直接插入排序呢?

其实不是的,因为经过一次预排序不能保证需排序乱序数组接近有序,只能保证比没有预排序之前更加有序,经过一次预排序就直接让需排序乱序数组来直接插入排序的话效率没有多大提升!

而且如果只进行一次预排序的话,gap就是一个定值,gap是定值是不合适的。如果gap确定是3,但需排序乱序数组数据个数有10000个的话,每组就有300多个数据要排,不合适!

其实主流玩法已尽解决了这些个问题,只要进行多组预排序就行,我们来看希尔排序的完整代码再分析:

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

这样子我们将gap设置成变化的,gap会越来越小,当gap不等于1时是预排序。相当于第一轮预排序大概是3个数据为一组排;第二轮预排序大概是9个数据为一组排;第三轮预排序是27个数据为一组排……而且每一轮预排序过后就越有序。

我们再分析发现,gap一定会变成1,那就是让需排序乱序数组直接来一把直接插入排序,这把直接插入排序过后循环结束并且需排序乱序数组就变成有序的了,而且经过了多次预排序最后来一把直接插入排序时间效率会很高!

当然gap如何变化都没有规定,我们也可以写成gap=gap/2……反正要保证最后一次循环的时候gap要等于1,才能保证最后一把是让需排序乱序数组整体进行直接插入排序。

我们来试试希尔排序得不得行:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>//希尔排序排升序
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int j = 0; j < n - gap; j ++){int end = j;int tmp = a[end + gap];for (end; end >= 0; end -= gap){if (tmp < a[end]){a[end + gap] = a[end];}else{break;}}a[end + gap] = tmp;}}
}void PrintArray(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}int main()
{int a[] = { 1,5,8,7,9,6,48,3,5,99,6,3,7,5 };PrintArray(a, sizeof(a) / sizeof(a[0]));ShellSort(a, sizeof(a) / sizeof(a[0]));PrintArray(a, sizeof(a) / sizeof(a[0]));return 0;
}

结果是没问题的!

2.希尔排序的时间复杂度

希尔排序有太多不确定性,所以时间复杂度不好计算,大多数人认为是O(N^1.3)。

3.希尔排序和直接插入排序的性能比较

也许老爷们会认为希尔排序弄那么多次预排序加一次直接插入排序,时间效率所不定还不如直接插入排序来的好。

其实不然,我们用一个程序比较比较就可以看出结果,这个程序用到一个C语言库里面的函数clock,clock函数的大致作用是获取从系统启动到调用这个clock函数之间的毫秒数。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>//希尔排序排升序
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int j = 0; j < n - gap; j ++){int end = j;int tmp = a[end + gap];for (end; end >= 0; end -= gap){if (tmp < a[end]){a[end + gap] = a[end];}else{break;}}a[end + gap] = tmp;}}
}//直接插入排序排升序
void InsertSort(int* a, int n)
{for (int j = 0; j < n - 1; j++){int end = j;int tmp = a[end + 1];for (end; end >= 0; end--){if (tmp < a[end]){a[end + 1] = a[end];}else{break;}}a[end + 1] = tmp;}
}int main()
{int n = 100000;int* a1 = (int*)malloc(sizeof(int) * n);int* a2 = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(0));for (int i = 0; i < n; i++){a1[i] = rand();a2[i] = a1[i];}int begin1 = clock();ShellSort(a1, n);int end1 = clock();int begin2 = clock();InsertSort(a2, n);int end2 = clock();printf("ShellSort:%d\n", end1 - begin1);printf("InsertSort:%d\n", end2 - begin1);return 0;
}

我们看到结果,排序10万个一模一样的随机数,希尔排序用22毫秒,而直接插入排序用4490毫秒! 

感谢阅读!

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

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

相关文章

浅析扩散模型与图像生成【应用篇】(二十一)——DALLE·2

21. Hierarchical Text-Conditional Image Generation with CLIP Latents 该文提出一种基于层级式扩散模型的由文本生成图像的方法&#xff0c;也就是大名鼎鼎的DALLE2。在DALLE2之前呢&#xff0c;OpenAI团队已经推出了DALLE和GLIDE两个文生图模型了&#xff0c;其中DALLE是基…

[机器学习-02] 数据可视化神器:Matplotlib和Seaborn工具包实战图形大全

目录 引言 正文 01-Matplotlib包的使用示例 1&#xff09;Matplotlib导入方式 2&#xff09;折线图绘制 3&#xff09;散点图绘制 4&#xff09;柱状图绘制 5&#xff09;饼图绘制 6&#xff09;等高线图绘制 7&#xff09;箱线图绘制 8&#xff09;较为复杂…

各城市-人口就业和工资数据(1978-2022年)

这份数据收集了1978年至2022年间300多个地级市的人口、就业和工资等数据。涵盖的指标包括从业人员数量、平均工资水平、人口密度等&#xff0c;通过这些数据可以深入了解中国各地城市的人口结构、就业状况以及工资水平的变化趋势。这些数据对于研究城市发展、劳动力市场以及区域…

HCIP-Datacom-ARST必选题库_BGP【道题】

1.关于summary automatic命令和BGP聚合的描述,错误的是? 该命令用于实现自动聚合,其优先级高于手动聚合 配置该命令后,BGP将按自然网段聚合路由 该命令用来使能对本地引入的路由进行自动聚合 配置该命令后,BGP只向对等体发送聚合后的路由 1.关于summary automatic命令和BGP聚…

PHP+MySQL组合开发微信公众号小程序二合一分销商城源码系统 带完整的安装代码包以及搭建教程

在当今数字化时代&#xff0c;电子商务的迅猛发展为各行各业带来了无限的商机和挑战。为了满足企业对线上销售平台的多样化需求&#xff0c;我们基于PHP和MySQL技术&#xff0c;开发了一款微信公众号小程序二合一分销商城源码系统。该系统不仅具备高度的可扩展性和稳定性&#…

Java新手必看:快速上手FileOutPutStream类

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

【Redis】RDB持久化和AOF 持久化

分布式缓存 单点 Redis 的问题 数据丢失&#xff08;持久化&#xff09;并发能力不如集群&#xff08;主从集群、读写分离&#xff09;Redis宕机导致服务不可用&#xff08;Redis哨兵&#xff09;存储能力差&#xff08;分片集群&#xff09; Redis 持久化 RDB 持久化 什么…

小微公司可用的开源ERP系统

项目介绍 华夏ERP是基于SpringBoot框架和SaaS模式的企业资源规划&#xff08;ERP&#xff09;软件&#xff0c;旨在为中小企业提供开源且易用的ERP解决方案。它专注于提供进销存、财务和生产功能&#xff0c;涵盖了零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询…

linux 调试-kdb 调试内核-1

目标&#xff1a;打印bcm2835_spi_transfer_one 是如何从用户空间开始调用的 1. kernel 配置 KDB配置选项 添加 spi 控制器驱动 和 spi 设备驱动 2. 调试流程 调试内核-系统启动之后 1. 开发板进入kdb,等待pc 连接 rootraspberrypi:~# echo "ttyS0,115200"…

文件加密软件排行榜前五:好用的文件加密软件推荐

后台有很多老板留言&#xff0c;说最近机密数据外泄的事情频发&#xff0c;让自己开始有了危机意识&#xff0c;想要提前针对企业安全问题采取措施&#xff0c;比方说选一款适合防泄密软件&#xff0c;但是不知道如何选择。 下面介绍几款软件&#xff0c;让大家了解一下市面上常…

贪吃蛇项目(小白保姆级教程)

游戏介绍 游戏背景&#xff1a; 贪吃蛇游戏是经典的游戏项目之一&#xff0c;也是很简单的小游戏 实现背景&#xff1a; 这里我们是基于32位的Win32_API进行实现的 需要的知识点&#xff1a; C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32_API等 适合人群&a…

Android:弹出对话框方式梳理一览(一)

Android&#xff1a;弹出对话框方式梳理一览&#xff08;一&#xff09; Guide&#xff5c;导言 在Android开发中&#xff0c;对话框可能是我们与用户交互的非常常用的方式&#xff0c;包括弹出一个小界面&#xff0c;可能在实际场景中都非常实用。本篇文章主要就是对Android弹…