一篇文章讲透排序算法之希尔排序

news2024/11/25 13:41:34

希尔排序是对插入排序的优化,如果你不了解插入排序的话,可以先阅读这篇文章:插入排序

目录

1.插入排序的问题

2.希尔排序的思路

3.希尔排序的实现

4.希尔排序的优化

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


1.插入排序的问题

如果用插入排序对一个逆序有序的数组排序时,时间复杂度为O(n^2),此时效率最低。

如果用插入排序对一个顺序有序的数组排序时,时间复杂度为O(n),此时效率最高。

我们发现,被排序的对象越接近有序,插入排序的效率越高,这时希尔就有了一个想法:如果可以将数组变得接近有序后再用插入排序呢?

2.希尔排序的思路

希尔排序是对插入排序的优化,它的思路是先选定一个整数作为增量,这里我们以gap(间隔)表示,将间隔为gap的数据分为一组,这样就可以分出gap组以gap为公差的等差数列的数据组。之后将这些数据组排序(把每组数据排序),之后将gap缩小,继续分组并进行排序,重复上述动作,直到gap缩小至1,此时排完了之后刚好有序。

为了让数组更接近有序的排序称为预排序,而最后一次排序是直接插入排序,而由于前面的操作使数据变得接近有序,因此最后一次直接插入排序需要移动的数据很少,效率便很高了。

下面我们来实现希尔排序。

现在我们给定如下数组,并以3为gap,可将数组根据颜色分为3组以3为公差的等差数列。

之后我们对这三组数据进行插入排序

之后我们将间隔缩小, 以2为间隔,我们就可以分出两组以2为公差的等差数列。

这里也并不一定要只减少1,减少多少看我们想减少多少。

现在我们完成第二次排序

现在我们的数组已经非常接近有序,我们最后再以1为间隔,得到一组以1为间隔的等差数列,再完成最后一次排序,也就是直接插入排序,即可使得我们的数组有序。

3.希尔排序的实现

现在我们根据我们的思路来逐步实现希尔排序

第一步:以3为间隔,排序第一组绿色的

在已经学习了插入排序的基础上,我们来实现一下排序绿色

//代码中的n代表数组长度,后面的代码不再解释。
int gap = 3;
//n-gap后的数据为最后一组数据,而当i等于我们的前一组数据时
//排序的就是最后一组数据,因此结束条件为i<n-gap
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;
}

第二步:进行第一次排序  

由于我们先前已经实现了排序绿色的,而排序蓝色的和排序黄色的不过是起始位置不同,因此我们再嵌套一层循环即可。

for (int j = 0; i < gap; j++)
{
	int gap = 3;
    //n-gap后的数据为最后一组数据,而当i等于我们的前一组数据时
    //排序的就是最后一组数据,因此结束条件为i<n-gap
	for (int i = j; 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;
	}
}

现在我们已经完成了第一次排序,那么后面的排序我们控制gap即可

for (int gap = 3; gap > 0; gap--)
{
	for (int j = 0; i < gap; j++)
	{
		for (int i = j; 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.希尔排序的优化

这时有一位大佬给出了这么一个解决方法:

我们不再一次比较一个数据组,

而是先比较第一个数据组的第一个数据和第二个数据,

然后比较第二个数据组的第一个数据和第二个数据,

之后比较第三个数据组的第一个数据和第二个数据,

然后比较第二个数据组的第二个数据和第三个数据,

这么一直比较下去,就可以完成我们第一次预排序的效果。

如下图所示,相同颜色的线表示比较的数据。

代码如下所示: 

int gap = 3
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;
}

现在我们已经完成了第一趟的排序,接下来我们控制gap即可。

int gap = 3;
while (gap > 0)
{
	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;
	}
	gap--;
}

现在这段代码看起来就舒服多了。但是我们的gap就一定每次都减1吗?

我们之前说过,预排序是为了让数组更加有序,我们只要能够让数组更加有序就可以了,没有必要每次让gap减1,gap太大了反而会有一些副作用。

这时有一位大佬写了这么一个希尔排序: 

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

这里的第一趟循环以二分之数组长度为间隔,后续的循环每次都除以2。

到了最后一次循环之时,gap要么等于2,要么等于3;而它们除2都等于1。这样就保证了最后一次循环是直接插入排序,可谓是相当完美了。

现在我们将其封装在函数体内,完成最终版的希尔排序

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

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

我们发现我们最终版的希尔排序也拥有三层循环,于是乎我们大家就对希尔排序的效率产生了疑问.但是利用我们现有数学能力无法计算出希尔排序的时间复杂度,只能给出一个大致范围

下面给出严蔚敏教授数据结构书中的相关论述:

在这里也可以给大家大概画一下图,由于每次排序都会对后续的排序产生影响,因此我们后续的排序移动的数据会越来越少,因此效率还是比较高的。 

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

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

相关文章

结构体;结构成员访问操作符

结构体&#xff1a; 虽然c语言已经提供了内置类型&#xff0c;比如&#xff1a;char、short、int、long等&#xff0c;但还是不够用&#xff0c;就好比我描述一个人&#xff0c;我需要描述他的身高&#xff0c;体重&#xff0c;年龄&#xff0c;名字等信息&#xff0c…

手把手一起学习Python NumPy

NumPy 是用于处理数组的 python 库&#xff0c;NumPy 中的数组对象称为 ndarray&#xff0c;它提供了许多支持函数&#xff0c;使得利用 ndarray 非常容易。Numpy官方网址 NumPy 安装 使用pip安装NumPy 模块&#xff1a; pip install numpyNumPy 入门 创建numpy数组&#x…

python-绘制五星红旗(非标准)

完整代码如下&#xff1a; #五星红旗&#xff08;非标准版&#xff09; from turtle import* import math from random import* tracer(0) penup() goto(-640,220) pendown() color(gold,gold) begin_fill() for i in range(5): fd(150) right(144) # 大五角星 penup(…

【Redis】 关于 Redis 哈希类型

文章目录 &#x1f343;前言&#x1f38b;命令介绍&#x1f6a9;hset&#x1f6a9;hget&#x1f6a9;hexists&#x1f6a9;hdel&#x1f6a9;hkeys&#x1f6a9;hvals&#x1f6a9;hgetall&#x1f6a9;hmget&#x1f6a9;hlen&#x1f6a9;hsetnx&#x1f6a9;hincrby&#x1…

C++面向对象程序设计-北京大学-郭炜【课程笔记(十一)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;十一&#xff09;】 1、string&#xff08;重要知识点&#xff09;1.2、string的赋值和链接1.3、比较string1.4、子串1.5、交换string1.6、寻找string中的字符1.7、删除string中的字符1.8、替换string中的字符1.9、在str…

java项目之图书管理系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的图书管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 系统主要分为管理员角色和用…

宝塔PHP环境安装配置Xdebug

宝塔PHP环境安装配置Xdebug 安装XdebugVSCode安装插件编辑配置文件编辑配置运行调试断点快捷键其他 安装Xdebug 在宝塔中&#xff0c;找到PHP&#xff0c;打开管理页面&#xff0c;选择xdebug扩展&#xff0c;点击操作栏中的安装按钮&#xff08;这里已经安装过了&#xff0c;…

JavaFX学习教程二

一、JavaFX 体系结构 JavaFX 场景图(Scene Graph)是构建 JavaFX 应用程序的起点&#xff0c;一种树状数据结构&#xff0c;用于排列&#xff08;和分组&#xff09;图形对象&#xff0c;以便于逻辑表示。 stage:舞台&#xff0c;操作系统窗口的 JavaFX 表示&#xff0c;是所有…

【Python】 掌握 Flask 请求数据获取的艺术

基本原理 在Web开发中&#xff0c;Flask是一个用Python编写的轻量级Web应用框架。它被广泛用于快速开发简单的Web应用。当用户通过浏览器或其他客户端向服务器发送请求时&#xff0c;Flask需要能够接收和解析这些请求中的数据。这些数据可以是GET请求的查询字符串、POST请求的…

方言和大语言模型

方言多样性及其对语言模型的影响 语言的演变是不可避免的&#xff0c;反映并推动了重大的社会变革和传统。语言接触往往会推动我们说话方式的创新&#xff0c;在美国全球文化的影响下&#xff0c;一种新的叙事正在其语言织锦中展开。 例如&#xff0c;在佛罗里达州南部&#…

springboot2.x3.x的A项目(作为sdk)集成到启动B项目调用2

一 概述 1.1 说明 本博客记录的案例&#xff0c;逻辑是&#xff1a; 项目A读取配置文件&#xff0c;并在service类的方法进行打印输出。项目A作为sdk被项目B进行依赖&#xff0c; 在项目B启动后&#xff0c;进行调用&#xff0c;并且在B进行参数的配置&#xff0c;能够覆盖…

java技术:spring-secrity实现认证、授权

目录 一、依赖 二、逻辑图 三、代码设计 1、WebSecurityConfigurerAdapter的实现类 2、设计登录接口 config配置&#xff1a; 1&#xff09;UserDetailsService实现类重写&#xff1a; 2&#xff09;书写登录实现类&#xff08;调用authenticationManager、可以与后面的…

第十三期Big Demo Day聚焦Web3前沿,FaceN.AI项目路演揭幕创新技术

第十三期Big Demo Day活动即将于2024年5月28日在香港数码港的CyberArena隆重举行。FaceN.AI将亮相本次Big Demo Day&#xff0c;参与精彩的项目路演&#xff0c;展示其在跨链去中心化数字身份、On-chain to Off-chain数据应用、DIDFi探索以及元宇宙与AIGC人格化发展等领域的领先…

kubectl--的陈述式资源管理

目录 一 kubectl 1 查看版本信息 2 查看资源对象简写 3 查看集群信息 4 配置kubectl自动补全 5 node节点查看日志 二 基本信息查看 1 查看 master 节点状态 2 查看命令空间 3 查看命名空间为default的所有资源 4 创建命名空间app 5 删除命名空间app 6 指定pod控…

Magisk + JustTrustMe 安装配置

操作步骤&#xff1a; 安装 Magisk 面具&#xff08;手机root&#xff09;在面具中刷入 LSPosed框架安装 JustTrustMe在LSPosed框架中配置并启动 JustTrustMe 一&#xff0c;Magisk面具 请根据自己手机的机型去root并安装面具&#xff0c;参考链接&#xff1a; https://www…

【全开源】二手车置换平台系统小程序(FastAdmin+ThinkPHP+Uniapp)

二手车置换平台系统 特色功能&#xff1a; 车辆评估&#xff1a;系统提供车辆状况、性能和价值的评估功能&#xff0c;通过拍照、上传图片等方式自动识别车辆信息并给出估价建议&#xff0c;帮助买家和卖家更准确地了解车辆价值。 在线交易&#xff1a;平台提供在线购车、售车…

Ubuntu22.04虚拟机设置静态IP

虚拟机设置静态IP 按下电脑的 “win”键&#xff0c;在弹出的输入框中输入“控制面板”&#xff0c;选中控制面板 1.选择 “网络和Internet” 2.选择 “网络和共享中心” 3.选择 “更改适配器设置” 4.选择 “VMnet8”&#xff0c;双击打开 5.选择 “属性” 找到 “Internet …

WebService的wsdl详解

webservice服务的wsdl内容详解&#xff0c;以及如何根据其内容编写调用代码 wsdl示例 展示一个webservice的wsdl&#xff0c;及调用这个接口的Axis客户端 wsdl This XML file does not appear to have any style information associated with it. The document tree is shown…

【数据分析面试】53.推送消息的分布情况(SQL)

题目 我们有两个表&#xff0c;一个是 notification_deliveries 表&#xff0c;另一个是包含 created 和购买 conversion dates 的 users 表。如果用户没有购买&#xff0c;那么 conversion_date 列为 NULL。 编写一个查询&#xff0c;以获取用户转换前的推送通知总数的分布情…