【数据结构】第十六弹---C语言实现希尔排序

news2024/11/27 4:03:50

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、希尔排序( 缩小增量排序 )

1.1、预排序实现

1.2、希尔排序代码实现

1.3、代码测试

1.4、时空复杂度分析

1.5、性能比较

总结


上一弹我们学习了直接插入排序,通过时空复杂度分析,时间复杂度为O(N^2),一般情况效率较低,有没有对直接插入排序进行优化的排序呢???没错,我们这一弹讲解的排序就是对直接插入排序的优化的排序!!!
 

1、希尔排序( 缩小增量排序 )

希尔排序是一种基于插入排序的算法,通过引入增量的概念来改进插入排序的性能

希尔排序法又称缩小增量法。希尔排序法的基本思想是:将原始列表分成多个子列表先对每个子列表进行插入排序,然后逐渐减少子列表的数量,使整个列表趋向于部分有序,最后当整个列表作为一个子列表进行插入排序时,由于已经部分有序,所以排序效率高。这个过程中,每次排序的子列表是通过选择不同的“增量”来确定的。

动图如下: 

实现思路

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

1.1、预排序实现

预排序:

根据当前增量,数组被分为若干子序列,这些子序列的元素在原数组中间隔着固定的增量。对每个子序列应用插入排序。

假设当前增量为5:

首先,增量为5,我们将数组元素分为增量(5)个子序列,每个子序列由原数组中相隔增量位置上的元素组成。所以我们有如下子序列:

子序列1: 9,4
子序列2: 1,8
子序列3: 2,6

子序列4: 5,3
子序列5: 7,5


然后对每个子序列进行独立的插入排序:

子序列1排序后:4,9
子序列2排序后:1,8
子序列3排序后:2,6

子序列2排序后:3,5
子序列3排序后:5,7

一趟排序之后的数组:

4 1 2 3 5 9 8 6 5 7

完成了一轮希尔排序,此时整个数组并不完全有序,但是已经比原始的数组更接近有序了。然后减小增量,通常是将原来的增量除以2(或者除以3+1),现在选择下一个增量为 2,按照此排序规则继续预排序即可,直到增量为1时,则为直接插入排序,此时则排序完成。

一个子序列排序实现:

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

与直接插入代码不同的是,这里对end所加减的均为gap;

单次插入完成后,我们来控制单个子序列的整个过程,每实现一次排序,下一次插入的数据为end+gap。

单趟排序实现:

int gap;

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

这里for循环的条件为 i <n-gap 防止数组越界.

完成单个子序列的排序后,我们再对整个子序列排序:

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

外层循环(for (int j = 0; j < gap; j++))意在对每个以gap为间隔的分组进行遍历。

优化:

这串代码三层循环的逻辑是按照每一组排序完成后再进行下一组排序的,事实上我们可以不需要最外层的循环。

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

这里我们将原先代码中的i += gap修改为i++意味着这次不是按照一组一组进行了,是一次排序完每个组的第二个元素,再进行下一个元素的排序。 

1.2、希尔排序代码实现

我们先对预排序的增量进行分析:

gap越大,大的值更快调到后面,小的值更快调到前面,越不接近有序。
gap越小,大的值更慢调到后面,小的值更慢调到前面,越接近有序。
当gap为1,就是直接插入排序。

所以在实现希尔排序时,给gap固定值是行不通的。

因此,gap的值是应该随着n来变化的,实现多次预排。为了满足gap最终为1,博主推荐的方式是先将gap赋值成n,然后在排序的时候将gap赋值成gap/3+1(或者gap/2)

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//博主写的是/3+1也可以是gap/2
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
                {
					break;
                }
			}
			a[end + gap] = tmp;
		}
	}
}

这里无论gap是奇数还是偶数,这里gap最终都会除以到值为1。

在这里:

gap>1时是预排序,目的让其接近有序
gap=1时是直接插入排序,目的让其有序。
在gap=1时,已经十分接近有序了。

这里gap预排序次数还是有点多,因此我们可以再次进行修改,让gap每次除以3,为了使gap最后能回到1,我们进行加一处理。

 注意:

1. 此处都是每隔gap进行插入。

2. gap不是一定为gap/3 + 1,也可以是gap /2 ,原因是当gap等于1的时候就是直接插入排序,进行一次排序即可变成有序,所以只要最后的gap为1都是可以的。 

1.3、代码测试

测试代码:

//测试希尔排序
int main()
{
	int a[] = { 9,8,7,6,5,4,3,2,1,0 };//给一组数据
	int sz = sizeof(a) / sizeof(a[0]);//计算数组元素个数
	printf("排序前:\n");
	ArrayPrint(a, sz);
	ShellSort(a, sz);
	printf("排序后:\n");
	ArrayPrint(a, sz);
	return 0;
}

测试结果:

1.4、时空复杂度分析

希尔排序的时间复杂度并不固定,它依赖于所选择的间隔序列(增量序列)。直到今天,已经有多种不同的间隔序列被提出来,每种都有自己的性能特点。

《数据结构(C语言版)》--- 严蔚敏
 

《数据结构-用面相对象方法与C++描述》--- 殷人昆

时间复杂度:

因为咋们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(N^1.25) 到  O(1.6* N^1.25) 来算。

空间复杂度:

插入排序的空间复杂度为O(1),因为它是一个原地排序算法,不需要额外的存储空间来排序。

1.5、性能比较

我们在前面一弹提到了clock()函数可以获取程序启动到函数调用时之间的CPU时钟周期数,我们在这里通过具体的排序算法来进行比较性能。

注意:clock()函数的头文件是#include<time.h>,时间的单位为毫秒。

性能比较的思想是通过比较两个函数所运行的时间大小。通过clock计算排序前的程序运行的时间,再计算排序结束程序运行的时间,时间的差值则为排序运行的时间。

尽量使用release模式进行测试,因为release效率更高。

测试代码:

void TestOP()
{
	srand(time(0));//随机数种子
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);//动态开辟N个元素
	int* a2 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand() + i;//随机数只有3万,为了更加随机再加上i
		a2[i] = a1[i];
	}
    //clock计算程序运行到此时的时间 毫秒
	int begin1 = clock();//排序前程序运行时间
	InsertSort(a1, N);
	int end1 = clock();//排序后程序运行时间
	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();
	printf("InsertSort:%d\n", end1 - begin1);//程勋运行时间的差值即排序运行的时间
	printf("ShellSort:%d\n", end2 - begin2);
	free(a1);//释放空间
	free(a2);
}

当N为10万时,release版本测试出来的结果: 

 

 当N为100万时,release版本测试出来的结果: 

明显能够看到希尔排序的效率比直接插入排序的效率高很多,当N为10万的时候,希尔排序是直接插入排序的18倍,当N为10万的时候,希尔排序是直接插入排序的20倍。

希尔排序的特性总结:

时间复杂度:O(N²)

空间复杂度:O(1)

稳定性:不稳定

复杂性:简单

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

基于mybatis plus增加较复杂自定义查询以及分页

基于java技术&#xff0c;spring-boot和mybatis-plus搭建后台框架是现在非常流行的技术。 其中关于多表关联查询的实现相对比较复杂&#xff0c;在这里进行记录以供开发人员参考。 以一个系统中实际的实体类为查询为例&#xff0c; T3dMaterial实体其中的fileType属性及字段…

C#批量设置海康和大华录像机NVR,GB28181的通道编码.

我经常要把小区海康或者大华的硬盘录像机推送到自己搭建的gb28181监控平台,每次几百个摄像头编码,有点头大,就用了1个多周写了个批量设置海康和大华硬盘录像机的通道编码的程序,海康和大华的SDK简直不是人看的. 太乱了. 大华读取通道编码的代码 /// <summary>/// 获取通道…

找我设计官网的不多了,看到漂亮大气的,还是忍不住分享出来。

现在有客户找我做官网设计&#xff0c;我说&#xff1a;要么搞个高大上个性化定制的&#xff0c;要么就选个模板得了&#xff0c;几千元的网站不上不下&#xff0c;不如不做。 分享一批高大上的网站给老铁们看看。

【课程系列01】某乎的AI大模型全栈工程师-第4期

网盘链接 链接&#xff1a;https://pan.baidu.com/s/1QLkRW_DmIm1q9XvNiOGwtQ --来自百度网盘超级会员v6的分享 课程目标 AI大模型全栈工程师是指具备人工智能领域全方位能力的工程师&#xff0c;特别是在大模型开发和应用方面具有深厚的专业知识和技能。以下是关于AI大模型…

跻身中国市场前三,联想服务器的“智变”与“质变”

IDC发布的《2024年第一季度中国x86服务器市场报告》显示&#xff0c;联想服务销售额同比增长200.2%&#xff0c;在前十厂商中同比增速第一&#xff0c;并跻身中国市场前三&#xff0c;迈入算力基础设施“第一阵营”。 十年砺剑联想梦&#xff0c;三甲登榜领风骚。探究联想服务器…

10分钟部署一个个人博客

关于vuepress这里没必要过多介绍&#xff0c;感兴趣的可以直接去官网了解&#xff0c;下面是官网首页地址截图 &#xff1a;https://v2.vuepress.vuejs.org/zh/ 透过这张图&#xff0c;我们也可以大致的对这个框架的特点有一定的认识&#xff0c;这就够了。其他的东西我们在使用…

Vue使用vue-esign实现在线签名 加入水印

Vue在线签名 一、目的二、样式三、代码1、依赖2、代码2.1 在线签名组件2.1.1 基础的2.1.2 携带时间水印的 2.2父组件 一、目的 又来了一个问题&#xff0c;直接让我在线签名&#xff08;还不能存储base64&#xff09;&#xff0c;并且还得上传&#xff0c;我直接***违禁词。 好…

【数据结构】——常见排序

文章目录 一、 冒泡排序二、 选择排序三、插入排序四、 快速排序1. hoare版本2. 优化版本3. 前后指针法4. 非递归版本 五、 堆排序六、 希尔排序七、 归并排序1. 递归版本2. 非递归版本 八、 计数排序 在开始之前先准备一个交换数据的函数&#xff0c;排序会经常用到 //交换位置…

【设计模式深度剖析】【8】【行为型】【备忘录模式】| 以后悔药为例加深理解

&#x1f448;️上一篇:观察者模式 设计模式-专栏&#x1f448;️ 文章目录 备忘录模式定义英文原话直译如何理解呢&#xff1f; 3个角色1. Memento&#xff08;备忘录&#xff09;2. Originator&#xff08;原发器&#xff09;3. Caretaker&#xff08;负责人&#xff09;类…

FPGA - 数 - 加减乘除

一&#xff0c;数的表示 首先&#xff0c;将二进制做如下解释&#xff1a; 2的0次方1 2的1次方2 2的2次方4 2的3次方8 ..... 以此类推&#xff0c;那么任何整数&#xff0c;或者说任意一个自然数均可以采用这种方式来表示。 例如&#xff0c;序列10101001&#xff0c;根据上述…

【数据挖掘】机器学习中相似性度量方法-欧式距离

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

三运放仪表放大器通过设置单个电阻器的值来调整增益

从公式 1 中可以看出&#xff0c;我们可以通过调整单个电阻器 R G的值来调整仪表放大器的差分增益。这很重要&#xff0c;因为与电路中的其他电阻器不同&#xff0c; RG的值不需要与任何其他电阻器匹配。 例如&#xff0c;如果我们尝试通过更改 R 5的值来设置增益&#xff0c;…

PHP杂货铺家庭在线记账理财管理系统源码

家庭在线记帐理财系统&#xff0c;让你对自己的开支了如指掌&#xff0c;图形化界面操作更简单&#xff0c;非常适合家庭理财、记账&#xff0c;系统界面简洁优美&#xff0c;操作直观简单&#xff0c;非常容易上手。 安装说明&#xff1a; 1、上传到网站根目录 2、用phpMyad…

Linux文本处理三剑客+正则表达式

Linux文本处理常用的3个命令&#xff0c;脚本或者文本处理任务中会用到。这里做个整理。 三者的功能都是处理文本&#xff0c;但侧重点各不相同&#xff0c;grep更适合单纯的查找或匹配文本&#xff0c;sed更适合编辑匹配到的文本&#xff0c;awk更适合格式化文本&#xff0c;对…

牛客热题:兑换零钱(一)

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;兑换零钱(一)题目链接方法一&am…

学习资料分析

学习资料分析 速算运算 √截位直除分数比较等比修正其他速算方法基期与现期基本概念求基期求现期增长率与增长量增长相关统计术语求一般增长率比较一般增长率增长量比重比重相关公式求比重平均数倍数间隔增长乘积增长率年增长率混合增长率资料分析:主要测查报考者对文字、数字…

windows环境如何运行python/java后台服务器进程而不显示控制台窗口

1.通常我们在windows环境下使用Java或Python语言编写服务器程序&#xff0c;都希望他在后台运行&#xff0c;不要显示黑乎乎的控制台窗口&#xff1a; 2.有人写了一个bat文件: cd /d D:\lottery\server && python .\main.py 放到了开机自启动里&#xff0c;可是开机的…

“土猪拱白菜” 的学霸张锡峰,如今也苦于卷后端

大家好&#xff0c;我是程序员鱼皮&#xff0c;前几天在网上刷到了一个视频&#xff0c;是对几年前高考励志演讲的学霸张锡峰的采访。 不知道大家有没有看过他的演讲视频。在演讲中&#xff0c;衡水中学的学霸张锡峰表达了城乡孩子差距大、穷人家的孩子只想要努力成为父母的骄…

港理工最新综述:基于LLM的text-to-SQL调查(方法实验数据全面梳理)1

【摘要】文本到SQL旨在将自然语言问题转换为可执行的SQL语句,这对用户提问理解、数据库模式理解和SQL生成都是一个长期存在的挑战。传统的文本到SQL系统包括人工工程和深度神经网络。随后,预训练语言模型(PLMs)被开发并用于文本到SQL任务,取得了可喜的成绩。随着现代数据库变得…

02_01_SpringMVC初识

一、回顾MVC三层架构 1、什么是MVC三层 MVC是 模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;的简写&#xff0c;是一种软件设计规范。主要作用是降低视图与业务逻辑之间的双向耦合&#xff0c;它不是一种…