【数据结构】希尔排序(缩小增量排序)

news2024/9/16 11:13:24

目录

一、基本思想

1.1 引入希尔排序的原因

1.2 基本思想

二、思路分析

三、gap分组问题

四、代码实现

4.1 代码一(升序)

4.2 代码二(升序)

五、易错提醒

六、时间复杂度分析

七、排序小tips


一、基本思想

1.1 引入希尔排序的原因

为什么要引入希尔排序呢?

因为直接插入排序的缺点就是怕逆序,如果待排的序列是逆序的情况,那么效率将直接下降,而希尔排序就是对直接插入排序的一个优化。

那什么是希尔排序呢?

1.2 基本思想

希尔排序法又称缩小增量法。希尔排序法的基本思想是:

将待排序的序列划分成若干个子序列(也就是若干组),分别进行插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。

通俗来讲,希尔排序分为两步:

  1. 预排序(目的:让数组接近有序)
  2. 直接插入排序

二、思路分析

我们清楚直接插入排序是如何实现的,那预排序是如何实现的呢?

接下来我们结合图来详细讲解一下希尔排序中预排序的过程:

这里以升序为例:

预排序完成之后,一组,二组,三组分别都是一组有序的序列,

虽然没有达成整个序列的有序,但相比之前,更加接近有序。

在此基础上,进行直接插入排序,会比之前更快,

之前的9需要移动n次,而现在的9只需要移动3次,因为每跳一次,步长为3,跳的更远了,所以移动的速度也就更快了。

所以说,预排序针对逆序情况特别凸显它的效率高效。

三、gap分组问题

预排序到底排几次比较合适,gap分为几组才是最恰当的呢?

我们知道:

gap越大,我们期望它可以跳的越快。大的数据就可以越快的跳到后面,小的数据也就可以越快的跳到前面。

当然,整个序列也就越不接近有序。

gap越小,它就跳的越慢,整个序列也就越接近有序。

当gap>1的时候,是预排序

当gap==1的时候,就是直接插入排序。

gap的取值方式有很多种,研究发现,gap=gap/3;是相对比较合理的,但是除以3并不能保证最后一次gap==1(也就是无法进行直接插入排序,因为希尔排序=预排序+直接插入排序)

所以,gap=gap/3+1;就可以保证最后一次gap一定等于1,也就是可以进行直接插入排序。

当然我们也可以选择gap=gap/2,但最好还是使用gap=gap/3+1。

四、代码实现

4.1 代码一(升序)

//一组一组执行
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//+1保证最后一次gap一定是1
		//gap>1 预排序
		//gap==1 直接插入排序
		gap = gap / 3 + 1;

		//完成一趟预排序
		for (int j = 0; j < gap; j++)
		{

			//完成一组的排序
			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;
			}
		}

	}
}

4.2 代码二(升序)

//多组并行走
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;

			}
	}
}

五、易错提醒

提醒一

代码一和代码二是两种执行次序,具体执行步骤如下所示:

代码一

代码二

代码一和代码二在效率执行上并没有任何区别,因为它们完成的是同一个过程。


提醒二

注意临界问题,for循环里i<n-gap的原因:

六、时间复杂度分析

希尔排序的平均时间复杂度是:O(N^1.3)

为什么呢?

我们可以进行一下简单的推导:

一共有gap组数据,gap=gap/3+1,这里假设忽略掉+1(目的是为了方便计算),变成gap=gap/3,

那么每组就有3个数据,因为每组数据个数=n/gap(n为数据总数)

例:下图n=12,共有12个数据,每组有3个数据,所以共分了4组,也就是gap=n/3=4.

这里千万不要混淆gap组的意思,根据图来进行理解

我们知道,一趟预排序的消耗次数=每组的比较次数*组数

所以,

第一趟预排序消耗的次数(最坏情况下):(1+2)*n/3=n   

(因为gap=gap/3=n/3,所以每组3个数据)

1表示:一组序列的第二个元素最坏比较多少次

2表示:一组序列的第三个元素最坏比较多少次

n/3表示:一共有多少组

第二趟预排序消耗的次数(最坏情况下):(1+2+3+......+8)*n/9=9*8/2*n/9=4n

(因为gap=gap/3=n/3/3=n/9,所以每组9个数据)

依次类推......

这里需要注意的是:

        当进行了第一趟最坏情况下的预排之后,第二趟不一定最坏了,因为第一趟预排序对第二趟预排有一定的影响。

        严格来说第二趟到不了最坏的情况,它应该比4n要小,那具体是多少,就很难算出来,这里涉及复变函数,概率等知识的运用。

所以说到最后一趟,也就是当gap==1时,就是直接插入排序

此时的序列已经非常接近有序了。

 所以,

最后一趟预排序消耗次数(正常情况下):n

这里可以用函数关系图来表示希尔排序比较次数和gap之间的关系:

从图中我们可以看出他们之间大致关系如上所示,经推算希尔排序的平均时间复杂度为:O(N^1.3)


这里我们也可以用另一种方式来求解时间复杂度。

可以计算一下走了多少趟预排序,也就是gap变化了几次,外层while循环走了多少次?

分析如下图所示:

所以研究希尔排序的最好时间复杂度是没有任何意义的。


提醒:

  1. 希尔排序的时间复杂度不好计算,因为gap取值方式的不同,会对排序效率有直接影响,这就导致了希尔排序的时间复杂度计算变得复杂,很难给出一个确切的复杂度,因此很多资料中希尔排序的时间复杂度都不固定。
  2. 我们上述分析的是按照Knuth提出的方式取值的,而Knuth进行了大量试验统计资料得出,当n很大时关键码平均比较次数和对象平均移动次数大约在 O(n^1.25)到 O(1.6n^1.25)范围内。

        故,我们直接按照O(N^1.3) 来计算即可。

七、排序小tips

我们在进行任何排序都要先处理好单趟的问题,然后再考虑整体,这样可以使我们写代码的思路更加清晰明了,也更容易写出正确高效的代码。

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

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

相关文章

Vue3:<Teleport>传送门组件的使用和注意事项

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 Vue3 引入了一个新的内置组件 <Teleport>&#xff0c;它允许你将子组件树渲染到 DOM 中的另一个位置&#xff0c;而不是在父组件的模板中直接渲染。这对于需要跳出当前组件的 DOM 层级结构进行渲染的…

15.1 JDBC数据库编程1

目录 15 引言 15.1.1 数据库语言SQL 15.2 JDBC体系结构 15.2.1 JDBC访问数据库 15.2.2 JDBC API介绍 15 引言 数据库系统&#xff08;database system,DBS&#xff09;由一个互相关联的数据集合和一组用以访问这些数据的程序组成。这个数据集合通常称为数据库。 …

音频-语言大模型原理

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

YOLOv8改进实战 | 注意力篇 | CloFormer: 注意力机制与卷积的完美融合CloAtention,即插即用

YOLOv8专栏导航:点击此处跳转 前言 YOLOv8 是由 YOLOv5 的发布者 Ultralytics 发布的最新版本的 YOLO。它可用于对象检测、分割、分类任务以及大型数据集的学习,并且可以在包括 CPU 和 GPU 在内的各种硬件上执行。 YOLOv8 是一种尖端的、最先进的 (SOTA) 模型,它建立在以前…

(C++) 6大作用域

文章目录 &#x1f365;前言&#x1f365;C 6大作用域&#x1f41f;块&#x1f41f;名字空间&#x1f41f;类&#x1f41f;函数参数&#x1f41f;枚举&#x1f41f;模板参数 ⭐END&#x1f31f;交流方式 &#x1f365;前言 在 C core guidelines 中有一个准则&#xff1a; ES.…

深入探索Unity协程:揭开CSharp迭代器背后的神秘面纱

协程是一种特殊类型的迭代器方法&#xff0c;允许你在多个帧之间分段执行代码。可以用来处理时间延迟、异步操作和顺序执行的任务&#xff0c;而不阻塞主线程。Unity协程的实现依赖于C#语言提供的迭代器相关的语言特性&#xff0c;所以想要弄清楚Unity协程的底层原理&#xff0…

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配&#xff08;Exact Match&#xff09;2. 正则表达式匹配&#xff08;Regex Match&#xff09;3. 前缀匹配&#xff08;Prefix Match&#xff09; 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中&#xff0…

Idea Mac代码调试常用快捷键~

Mac截图 commandShift4 idea英文大写转小写 commandShiftU 功能&#xff1a;查看类的实现和继承父类的方法 快捷键 fncommandF12 鼠标点击打开 功能&#xff1a;查看当前方法的上游方法 选中方法&#xff0c;controloptionH 功能&#xff1a;CommandB是查看本类的方法 功能&…

Matlab simulink建模与仿真 第十一章(端口及子系统库)【下】

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 八、触发使能子系统 1、Enabled and Triggered Subsystem触发使能子系统概述 触发使能子系统其实是触发子系统和使能子系统二者的结合&#xff0c;当触发端口传来触发信号时&#xff0c;使能端口的输入需要大…

TitleBar:打造高效Android标题栏的新选择

在Android应用开发中&#xff0c;标题栏是用户界面的重要组成部分。一个好的标题栏不仅能够提升应用的专业感&#xff0c;还能增强用户体验。然而&#xff0c;传统的标题栏实现方式往往存在代码冗余、样式不统一、性能开销大等问题。今天&#xff0c;我们将介绍一个名为TitleBa…

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐&#xff1f; 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识&#xff0c;并举出了两个例子&#xff0c;我们再举出两个例子继续说明&…

python进阶篇-day08-数据结构与算法(线性结构介绍与链表实现)

数据的存储和组织形式 程序 数据结构 算法 一. 算法介绍 概述目的 都是可以提高程序的效率(性能), 面试高频考点 数据结构介绍 数据的存储和组织形式, 同样的空间, 不同的结构, 存储的数据不同, 操作方式也不同 算法介绍 为了解决实际的业务问题, 而考虑出来的方法和思路 …

龙芯+FreeRTOS+LVGL实战笔记(新)——06添加二级按钮

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼&#xff1f;市面上那么多工具&#xff0c;常常让人无从选择。今天&#xff0c;想给大家安利一款神器——PixPin&#xff0c;一款真正解放双手的截图工具。 想象一下&#xff0c;你只需要按下快捷键就能轻松完成多种截…

雷电9模拟器安装magisk和lsposed

模拟器环境配置 1、开启root 2、开启System.vmdk可写入 安装magisk 1、新建模拟器、开启root权限、并安装debug版magisk 下载地址去上面吾爱论坛作者文章下载吧&#xff01;支持他一下&#xff01; 2、打开magisk的app&#xff0c;点击安装 如果弹出获取权限&#xff0c;直接…

【Socket网络编程原理实践】

socket 基于 TCP/IP协议实现&#xff0c;在网络模型中属于传输层 Java 网络编程中的核心概念 IP 地址&#xff1a;用于标识网络中的计算机端口号&#xff1a;用于标识计算机上的应用程序或进程Socket&#xff08;套接字&#xff09;&#xff1a;网络通信的基本单位&#xff0…

冒泡排序算法介绍

冒泡排序算法介绍 如果真的累了&#xff0c;就拉上窗帘关上手机关掉闹钟深呼吸一口气钻进被窝&#xff0c;好好地睡一觉&#xff0c;难熬的日子总需要一些温暖&#xff0c;而什么都不如被窝的温暖来的踏实。 冒泡排序是一种经典的排序算法&#xff0c;它通过重复遍历待排序的序…

如何恢复回收站中已删除/清空的文件

回收站清空后如何恢复已删除的文件&#xff1f;是否可以恢复永久删除的文件&#xff1f;或者最糟糕的是&#xff0c;如果文件直接被删除怎么办&#xff1f;本文将向您展示清空回收站后恢复已删除数据的最佳方法。 回收站清空后如何恢复已删除的文件&#xff1f; “回收站清空后…

从零开始搭建GPU深度学习环境(pytorch)

傻乎乎的我&#xff0c;突然发现我自己的笔记本电脑居然有gpu&#xff0c;这个电脑是我弟在2017年购入的。 电脑已经按照了cpu环境&#xff0c;现在增加gpu环境 参考torch的cpu版本和gpu版本有什么区别 torch与cuda版本_mob64ca13f6035c的技术博客_51CTO博客 前言&#xff1a…

Vue3使用Uni-ui的popup弹出层组件

由于uni-ui中有些组件文档的基于vue2编写的&#xff0c;比如popup组件 下面是vue3的写法 除了文档中要求的aleterDialog外&#xff0c;还得利用v-if设置一个isDialog判断 // template // script 解决