排序算法之【希尔排序】

 

📙作者简介: 清水加冰,目前大二在读,正在学习C/C++、Python、操作系统、数据库等。

📘相关专栏:C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。

欢迎点赞 👍 收藏 ⭐留言 📝 如有错误还望各路大佬指正!

✨每一次努力都是一种收获,每一次坚持都是一种成长✨       

 

a6c0473e16e249c2b9ca02e5b793f35e.gif#pic_center

 前言

        希尔排序,作为一种高效的排序算法,更是在排序算法这个舞台上展现出了自己独特的魅力,听这个名字就感觉这个排序不简单,本篇文章我将带领大家深入了解希尔排序。


1. 排序算法

         在数据世界中,排序算法是一种强大的工具,能够将混乱无序的数据变得井然有序,排序算法有很多种,最常见也是最常用的排序算法有8种,本期的主角是希尔排序。

1.1插入排序

         希尔排序属于插入排序的一种,在理解希尔排序之前,我们需要先来了解一下插入排序的初阶——直接插入排序

 1.1.1 直接插入排序

         直接插入排序的原理非常好理解,举个例子:我们在完扑克牌时都会对牌的大小进行排序(也就是组成顺子)。

155068a8c5a8473495e7f9a30a2b6f03.png

         这就利用了直接直接插入排序,7和10比较,7小,7再和5比较,比5大,所以7就插入到10的前边。

        直接插入排序,是一种简单直观的排序算法,它的基本思想是将一个待排序的元素插入到已经排好序的元素序列中的适当位置,从而得到一个新的、个数加一的有序序列。具体步骤如下:

  1. 将待排序序列第一个元素视为已排序序列,将其作为比较的基准。
  2. 从第二个元素开始,依次将每个元素插入到已排序序列中的适当位置。
  3. 每次插入一个元素时,都将该元素与已排序序列中的元素进行比较,找到合适的位置插入。
  4. 插入时,将已排序序列中比待插入元素大的元素向后移动一位,为待插入元素腾出位置。
  5. 重复步骤3和步骤4,直到将所有元素插入到已排序序列中。

 过程如下图:

8419359b14624acb898e43c1cedbd2f5.gif

         理解了基本原理,我们来进行代码实现,在写排序算法时我们可以先写一趟的逻辑。我们需要记录开始比较的位置,还要记录开始位置的后一个数据,例如当end=0时,我们需要记录end的后一个位置数据,从end开始一次向前移动并比较大小。如果tmp小于end位置的数据,那么,end位置的数据就向后移动一个位置。在这个过程中end要不断减减。具体代码如下:

	int end = ;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];}else{break;}end--;}arr[end + 1] = tmp;//找到比tmp大的数据后跳出循环插入到end的后边
}

        这也仅仅是一个数据插入的过程,接下来我们要搞定这个数组所有的元素。我们可以利用一个for循环,观察动图我们可以发现,end是逐个向后的。但我们需要考虑越界的问题,i要小于n-1,结束位置在最后的前一个位置。完整代码如下:

void InsertSort(int* arr,int n)
{for (int i = 0; i < n-1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];}else{break;}end--;}arr[end + 1] = tmp;}
}

 1.1.2 希尔排序

 由来

        通过上述的插入排序过程我们可以发现,插入排序的时间复杂度不低,在局部有序的情况下,插入排序很优。但存在最坏的情况,就是逆序。逆序的情况下,它的时间复杂度不亚于冒泡排序。

        为了防止出现较坏的情况,有位叫希尔的大佬对插入排序进行了优化,就是在插入排序之前进行预排序。所以希尔排序的过程就可分为两部分:

  1. 预排序
  2. 插入排序

 过程

      希尔排序(Shell Sort),也称为缩小增量排序,是插入排序的一种改进算法。它通过将待排序序列分割成若干子序列来进行排序,最终将整个序列排序。

        希尔排序的基本思想是先将待排序序列按照一定的增量进行分组,对每个子序列进行插入排序,然后逐步缩小增量,再次进行分组和插入排序,直到增量为1,完成最后一次插入排序,整个序列就变成有序的。

  预排序     

         上边说到按照增量进行分组,不好理解,我们可以理解为步数(gap),当gap为1的时候就是插入排序,gap为1就是相邻数据依次比较进行插入。当gap > 1时都是预排序,目的是让数组更接近于有序。

        例如gap等于4,那进行比较时就是对相邻间隔为4的数据进行调整位置插入。如下图:

2b43134d5e6344d68513ee6d110a44ad.gif

 gap越小,数据就越接近有序。

 1.1.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];}else{break;}end -= gap;}a[end + gap] = tmp;
}

        这段代码看着是否很熟悉,当gap为1时,这段代码就是直接插入排序。这个是从第一个数据开始,将数组中距离为gap数据进行调整。

        那么接下来还要从第二个数据开始,将距离为gap的数据进行调整。所以这里我们可以加一个循环,确保每个数据都能被调整。

for (int i = 0; i < gap; i++)
{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];}else{break;}end -= gap;}a[end + gap] = tmp;}
}

这里我们可以优化一下,我们这样写:

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];}else{break;}end -= gap;}a[end + gap] = tmp;
}

        大多数看到的书中都是这样写的,或许在之前学习中,很多同学不懂for循环这里为什么这样写。起初的逻辑是,调整完一组后,再调整下一组。那我们可不可以同时调整所有组?

我们先调整第一个数据和它距离为gap的数据,不急着将整组数据调整完,接着开始从第二个数据开始与它距离为gap的数据进行调整……

         理解了这里,我们继续,上述的过程是在gap不变的情况下进行的,预排序的过程是不断的改变gap的值来进行预排序。gap越小预排序后数据越接近有序。

void ShellSort(int* a, int n)
{int gap = n;while (gap>1){gap = gap / 2;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];}else{break;}end -= gap;}a[end + gap] = tmp;}}
}

         我们可以先令gap等于n(数组的数据个数),然后使用while循环判断gap是否大于1,当gap > 1时都是预排序,进入while循环之后,gap就整除2。到这里希尔排序就实现了。任何一个大于2的数除2最后都等于1,除到最后gap等于2时进入while循环,先执行gap=gap/2,最后一次执行时就刚好是插入排序。

         当然gap也不一定就除2,在一些书中也有这样的写法:gap=gap/3+1,除3是因为有人觉得gap/2进行预排序的次数太多了,但gap/3并不能保证除到最后,所以就有了这种写法:gap=gap/3+1。

2. 复杂度

        了解了直接插入排序和希尔排序,那我们就来说一说它的复杂度问题,它们的空间复杂度都为O(1),重点就在它们的时间复杂度。

2.1  直接插入排序

         直接插入排序的时间复杂度好说,假设有n个数据,第一次从第一个数据开始,最多比较1次,第二次从第二个数据开始,最多比较2次,第三个最多比较3次……

那么它的执行次数T=1+2+3+4+……+n-1,等差数列求和,T=(n-1+1)*n/2(首项加尾项乘以项数除以2)所以它的时间复杂度最差达到O(N^2)。

2.2  希尔排序

         希尔排序的时间复杂度计算非常复杂,它的时间复杂度和gap有关。

当gap很大时,例如gap=n/3。整个数组逆序那么就有n/3组数据,每组数据最多比较3(1+2)次。

eb5ffef5ba684417b6f8a0c38d02e4a2.png

 合计比较:n/3*3=n。

 当gap等于n/9时,整个数组逆序,就会有n/9组数据,每组有9个数据,每组最多比较(1+2+3+4+5+6+7+8)次

 合计比较:n/9*36=4n。

 当gap很小时,如gap等于1时,就只有1组数据进行调整排序,此时的数据已经非常接近有序了,再进行调整,最差会比较n次,估算一下,合计:n

 由此可以看出:希尔排序的性能图形为一个曲线函数。

希尔排序时间复杂度计算分析,以上内容为了解

         希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在很多书中给出的希尔排序的时间复杂度都不固定。在实际测试效果时,众多答案中,最符合的是O(N^1.3)。从这里就可以看出,希尔排序它非常的不稳。


总结 

         本篇文章主要介绍了希尔排序,希尔排序的时间复杂度与增量序列的选择有关,它是一种不稳定的排序算法,适用于中等规模的数据排序。希望本文对你有所帮助,最后,感谢阅读!

 

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

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

相关文章

C++ -- 学习系列 std::deque 的原理与使用

一 deque 是什么? std::deque 是 c 一种序列式容器&#xff0c;其与 vector 类似&#xff0c;其底层内存都是连续的&#xff0c;不同的地方在于&#xff0c; vector 是一端开口&#xff0c;在一端放入数据与扩充空间&#xff0c;而 deque 是双端均开口&#xff0c;都可以放…

3D孪生场景搭建:模型区域摆放

前面介绍完了NSDT场景编辑器的线性绘制和阵列绘制&#xff0c;本章将讲述下编辑器的另一种绘制方式&#xff1a;区域绘制。 1、区域绘制功能简介 在场景中绘制资产时&#xff0c;除使用上述两个的方式外&#xff0c;NSDT 编辑器还支持使用区域绘制的方式进行绘制。先选取需要…

GEO生信数据挖掘(一)数据集下载和初步观察

检索到目标数据集后&#xff0c;开始数据挖掘&#xff0c;本文以阿尔兹海默症数据集GSE1297为例 目录 GEOquery 简介 安装并加载GEOquery包 getGEO函数获取数据&#xff08;联网下载&#xff09; 更换下载数据源 对数据集进行初步观察处理 GEOquery 简介 GEOquery是一个…

聊聊并发编程——并发容器和阻塞队列

目录 一.ConcurrentHashMap 1.为什么要使用ConcurrentHashMap&#xff1f; 2.ConcurrentHashMap的类图 3.ConcurrentHashMap的结构图 二.阻塞队列 Java中的7个阻塞队列 ArrayBlockingQueue&#xff1a;一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue&#xf…

用go实现http服务端和请求端

一、概述 本文旨在学习记录下如何用go实现建立一个http服务器&#xff0c;同时构造一个专用格式的http客户端。 二、代码实现 2.1 构造http服务端 1、http服务处理流程 基于HTTP构建的服务标准模型包括两个端&#xff0c;客户端(Client)和服务端(Server)。HTTP 请求从客户端…

PHP8的静态变量和方法-PHP8知识详解

我们在上一课程讲到了public、private、protected这3个关键字&#xff0c;今天我们来讲解static关键字&#xff0c;明天再讲解final关键字。 如果不想通过创建对象来调用变量或方法&#xff0c;则可以将该变量或方法创建为静态变量或方法&#xff0c;也就是在变量或方法的前面…

【PyTorch实战演练】使用Cifar10数据集训练LeNet5网络并实现图像分类(附代码)

文章目录 0. 前言1. Cifar10数据集1.1 Cifar10数据集下载1.2 Cifar10数据集解析 2. LeNet5网络2.1 LeNet5的网络结构2.2 基于PyTorch的LeNet5网络编码 3. LeNet5网络训练及输出验证3.1 LeNet5网络训练3.2 LeNet5网络验证 4. 完整代码4.1 训练代码4.1 验证代码 0. 前言 按照国际…

C语言文件操作与管理

一、为什么使用文件 在我们前面练习使用结构体时&#xff0c;写通讯录的程序&#xff0c;当通讯录运行起来的时候&#xff0c;可以给通讯录中增加、删除数据&#xff0c;此时数据是存放在内存中&#xff0c;当程序退出的时候&#xff0c;通讯录中的数据自然就不存在了&#xff…

Java 基于 SpringBoot 的在线学习平台

1 简介 基于SpringBoot的Java学习平台&#xff0c;通过这个系统能够满足学习信息的管理及学生和教师的学习管理功能。系统的主要功能包括首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;课程信息管理&#xff0c;类型管理&#xff0c;作业信息…

大数据Doris(一):Doris概述篇

文章目录 Doris概述篇 一、前言 二、Doris简介

队列的各个函数的实现

1.第一个结构是存放链表的数据&#xff0c;第二个结构体是存放头节点和尾节点的以方便找到尾节点&#xff0c;存放头节点的是phead&#xff0c;尾节点的是ptail typedef struct QueueNode {struct QueueNode* next;//单链表QDataType data;//放数据 }QNode;typedef struct Queu…

并查集LRUCache

文章目录 并查集1.概念2. 实现 LRUCache1. 概念2. 实现使用标准库实现自主实现 并查集 1.概念 并查集是一个类似于森林的数据结构&#xff0c;并、查、集指的是多个不相干的集合直接的合并和查找&#xff0c;并查集使用于N个集合。适用于将多个元素分成多个集合&#xff0c;在…

脉冲法和方向盘转角法计算车辆位置不同应用工况

1. 脉冲法计算车辆位置 在定义下的世界坐标系中&#xff0c;车辆运动分为右转后退、右转前进、左转后退、左转前进、直线前进、直线后退和静止七种工况&#xff0c;因此需要推倒出一组包含脉冲、车辆运动方向和车辆结构尺寸参数的综合方程式进行车辆轨迹的实时迭代计算。由于直…

Linux:nginx---web文件服务器

我这里使用的是centos7系统 nginx源码包安装 Linux&#xff1a;nginx基础搭建&#xff08;源码包&#xff09;_鲍海超-GNUBHCkalitarro的博客-CSDN博客https://blog.csdn.net/w14768855/article/details/131445878?ops_request_misc%257B%2522request%255Fid%2522%253A%25221…

【AntDesign】封装全局异常处理-全局拦截器

[toc] 场景 本文前端用的是阿里的Ant-Design框架&#xff0c;其他框架也有全局拦截器&#xff0c;思路是相同&#xff0c;具体实现自行百度下吧 因为每次都需要调接口&#xff0c;都需要单独处理异常情况&#xff08;code !0&#xff09;&#xff0c;因此前端需要对后端返回的…

每日一博 - 闲聊 Java 中的中断

文章目录 概述常见的中断问题中断一个处于运行状态的线程中断一个正在 sleep 的线程中断一个由于获取 ReentrantLock 锁而被阻塞的线程 如何正确地使用线程的中断标识JDK 的线程池 ThreadPoolExecutor 内部是如何运用中断实现功能的小结 概述 在 Java 中&#xff0c;中断是一种…

应用在手机触摸屏中的电容式触摸芯片

触控屏&#xff08;Touch panel&#xff09;又称为触控面板&#xff0c;是个可接收触头等输入讯号的感应式液晶显示装置&#xff0c;当接触了屏幕上的图形按钮时&#xff0c;屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置&#xff0c;可用以取代机械式的按钮面板&…

ElementUI实现登录注册啊,axios全局配置,CORS跨域

一&#xff0c;项目搭建 认识ElementUI ElementUI是一个基于Vue.js 2.0的桌面端组件库&#xff0c;它提供了一套丰富的UI组件&#xff0c;包括表格、表单、弹框、按钮、菜单等常用组件&#xff0c;具备易用、美观、高效、灵活等优势&#xff0c;能够极大的提高Web应用的开发效…

Lua函数

--函数--无参无返回值 function F1()print("F1函数") end F1() print("*****************")--有参 function F2(a)print("F2函数"..a) end F2(2) --如果传入参数和函数数量不一致 --不会报错只是补空 F2(1,2) print("*****************&quo…

【夏虫语冰】测试服务器端口是否打开(命令行、Python)

文章目录 1、简介2、命令行2.1 telnet2.1.1 工具简介2.1.2 工具配置2.1.3 工具使用 2.2 curl2.2.1 工具简介2.2.1 工具下载2.2.1 工具使用 2.3 wget2.3.1 工具简介2.3.2 工具下载2.3.2 工具使用 2.4 nc2.4.1 工具简介2.4.2 工具安装2.4.3 工具使用 2.5 ssh2.5.1 工具简介2.5.2 …