希尔排序详解:一种高效的排序方法

news2024/11/25 14:54:25

在探索排序算法的世界中,我们经常遇到需要对大量数据进行排序的情况。传统的插入排序虽然简单,但在处理大规模数据时效率并不高。这时,希尔排序(Shell Sort)就显得尤为重要。本文将通过深入解析希尔排序的逻辑,帮助读者更好地理解这一高效的排序方法。

希尔排序的基本概念

希尔排序,由Donald Shell于1959年提出,是插入排序的一种改进版本。它通过引入“间隔因子”来分组进行插入排序,有效地减少了数据交换和移动的次数。希尔排序的核心思想是将整个待排序的记录序列分割成若干个子序列分别进行直接插入排序。

接下来,我们先从他的基础,插入排序讲解。

 直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。 实际中我们玩扑克牌时,就用了插入排序的思想

 

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与 array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定 

 

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int end = i;

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

探索希尔排序的代码逻辑

我们以C语言代码为例,详细解析希尔排序的实现过程:

 

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;  // 向前移动gap位置
                }
                else
                {
                    break;
                }
            }
            a[end + gap] = tmp;  // 插入元素
        }
    }
}

 在希尔排序中,我们可以将整个排序过程划分为“预排序”,和“直接插入排序”

预排序:分别对每个组进行插入排序

 

在上述代码中,我们采用嵌套关系来控制每一组进行预排序。也就是当<gap时,就有++j次的组进行预排序。所以gap就是组次 

 

 此时我们按照一组一组预排序,还需要外层嵌套for循环来控制,所以我们采用多组并排进行代码优化。

 

 此时相当于我们走了第一个红色,紧接着不走红色这组,我们++i来到蓝色这组,蓝色也走一次,再来到绿色,以此类推。当我们走到n-gap时,就代表我们三组并排,直接走完

此时预排序已经走完,接下来就是到如何预排序完进行插入排序

 

 所以gap的大小我们如何确定才能选择一个最为合适的gap呢,我们采用while循环.

和 ‘gap = gap/3 +1’  当我们最开始进行预排序时,很快,但是不够接近有序,所以我们不断缩小gap,一开始有人提出用gap /2 ,确实可以,但又有人发现/3效率更高,但/3有可能为0,所以我们进行+1 ,确保最后gap一定能为1,所以此while循环不断进行,直到gap==1,进行插入排序,此时上一层预排序后已经十分接近升序序列,所以这一层的时间复杂度接近O(n),最后经过研究,最接近希尔排序的时间复杂度是N^1.13

 我们采用Pop函数进行排序时间测试,排序代码见附录

 

可见希尔排序已经相对于冒泡和插入,已经十分优秀了。 

间隔序列的选择

在此代码中,间隔序列是关键。初始时,gap 设置为数组长度,然后逐步减小(gap = gap / 3 + 1),直至减到1。这种递减的间隔序列可以帮助我们在排序初期迅速减少大量的逆序对,从而提高排序效率。

排序过程

在每一个固定的间隔下,代码通过插入排序的方式对分组数据进行排序。随着间隔的不断减小,整个数组越来越接近于完全有序,当间隔减小到1时,整个数组实际上已经接近于完全有序状态,此时进行一次插入排序后,数组即变为有序。

希尔排序的效率和应用

希尔排序的时间复杂度与间隔序列的选择有很大关系,最佳情况下的时间复杂度可以达到O(n^1.13)。由于其独特的排序方式,希尔排序在处理大规模乱序数组时,相较于传统的插入排序有着显著的效率优势。它不仅适用于普通的数据排序任务,还在某些特定类型的数据处理场景中表现出色。

希尔排序的优缺点
  • 优点:

    • 相较于传统的插入排序,在处理大量数据时有更高的效率。
    • 算法结构简单,易于理解和实现。
    • 适用于大型数据集,特别是对于初期无序的数据排序效果显著。
  • 缺点:

    • 间隔序列的选择对性能有很大影响,但最优的间隔序列尚无定论。
    • 在最坏的情况下,性能可能并不理想。
    • 相对于更高级的排序算法(如快速排序、归并排序等),在一些特定情况下仍然效率较低。

附录: 

#define _CRT_SECURE_NO_WARNINGS
#include "Sort.h"
void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	//SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	//HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	//QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	//MergeSort(a6, N);
	int end6 = clock();

	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int end = i;

		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}
void BubbleSort(int* a, int n)//使用bool来进阶冒泡 ,当有一层不交换,就代表已经排完序,防止永久时间复杂度都是O(n^2)
{
	for (int j = 0; j < n; j++)
	{
		bool exange = false;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exange = true;
			}
		}
		if (exange == false)
			break;
	}

}
void ShellSort(int* a, int n)
    // gap > 1时是预排序,目的让他接近有序
	// gap == 1是直接插入排序,目的是让他有序
{
	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;
		}
	}
}

 

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定 

 

总结起来,希尔排序是一种高效且实用的排序算法,特别适用于大规模且初期无序的数据集。你的代码提供了一个很好的示例,展现了希尔排序的实现及其与其他排序算法相比的性能优势。希望这篇博客能帮助读者更好地理解和运用希尔排序算法。

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

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

相关文章

分享十几个适合新手练习的软件测试项目

说实话&#xff0c;在找项目的过程中&#xff0c;我下载过&#xff08;甚至付费下载过&#xff09;N多个项目、联系过很多项目的作者&#xff0c;但是绝大部分项目&#xff0c;在我看来&#xff0c;并不适合你拿来练习&#xff0c;它们或多或少都存在着“问题”&#xff0c;比如…

编写一个程序, 给出两个时间,计算出两个时间之差,如给出1120表示11:20,1330表示13:30, 将时间间隔以分钟为单位输出。

如下: #include<stdio.h>int main(){int a,b;printf("请输入第一个时间a:");scanf("%d",&a);printf("请输入第二个时间b:");scanf("%d",&b);int hour1a/100;//取小时int minute1a%100;//取分钟int hour2b/100;int minu…

HCIP --- BGP 基础 (中)

BGP的数据包 Open、Update、Notification、Keepalive、Route-refresh BGP的公共头部 Marker &#xff1a;标记 &#xff08;可以兼容字段、版本&#xff09; 全F Length&#xff1a; 标明数据包多长多大 Type&#xff1a;表明数据包类型&#xff08;可选 12345&#xff09; …

如何切换用户和更改用户密码

https://blog.csdn.net/u012759006/article/details/89681615 https://blog.csdn.net/Z_CAIGOU/article/details/120925716 1、sudo su 切换到root用户 2、passwd 用户名 之后输入你修改后的密码两次&#xff0c;成功。 文章知识点与官方知识档案匹配&#xff0c;可 一般情…

C语言 扫雷游戏

代码在一个项目里完成&#xff0c;分成三个.c.h文件(game.c,game.h,main.c) 在Clion软件中通过运行调试。 /大概想法/ 主函数main.c里是大框架(菜单,扫雷棋盘初始化&#xff0c;随机函数生成雷&#xff0c;玩家扫雷) game.h函数声明(除main函数和游戏函数外的一些函数声明) ga…

Kafka安全性探究:构建可信赖的分布式消息系统

在本文中&#xff0c;将研究Kafka的安全性&#xff0c;探讨如何确保数据在传输和存储过程中的完整性、机密性以及授权访问。通过详实的示例代码&#xff0c;全面讨论Kafka安全性的各个方面&#xff0c;从加密通信到访问控制&#xff0c;帮助大家构建一个可信赖的分布式消息系统…

品牌控价成本如何把控

品牌在发展&#xff0c;价格就需要持续关注&#xff0c;当出现乱价、低价、窜货时就应投入人力去治理&#xff0c;但企业生存&#xff0c;还要考虑成本&#xff0c;如何在保证控价效果的基础上&#xff0c;做到使用最低成本呢&#xff0c;这些问题除了控价本身外&#xff0c;也…

苹果IOS在Safari浏览器中将网页添加到主屏幕做伪Web App,自定义图标,启动动画,自定义名称,全屏应用pwa

在ios中我们可以使用Safari浏览自带的将网页添加到主屏幕上&#xff0c;让我们的web页面看起来像一个本地应用程序一样&#xff0c;通过桌面APP图标一打开&#xff0c;直接全屏展示&#xff0c;就像在APP中效果一样&#xff0c;完全体会不到你是在浏览器中。 1.网站添加样式 在…

去掉手机端顶部间隙

Unigui手机端打开时&#xff0c;在顶部有一条白色间隙 使用以下方法可以去除间隙 在ServerModule的customcss里添加以下代码 body{ margin:0!important; padding:0!important; }

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《考虑垃圾处理与调峰需求的可持续化城市多能源系统规划》

这个标题涵盖了城市多能源系统规划中的两个重要方面&#xff1a;垃圾处理和调峰需求&#xff0c;并强调了规划的可持续性。 考虑垃圾处理&#xff1a; 含义&#xff1a; 垃圾处理指的是城市废弃物的管理和处置。这可能涉及到废物分类、回收利用、焚烧或填埋等方法。重要性&…

使用pyftpdlib组件实现FTP文件共享

目录 一、引言 二、技术背景 三、实现逻辑 1、创建FTP服务器&#xff1a; 2、实现文件共享&#xff1a; 3、设置用户权限&#xff1a; 4、处理异常&#xff1a; 5、优化与扩展&#xff1a; 四、代码实现 五、测试与评估 测试用例&#xff1a; 评估方法&#xff1a;…

ubuntu22.04安装 nvidia-cudnn

nvidia-cudnn 是 NVIDIA CUDA 深度神经网络库&#xff08;CUDA Deep Neural Network library&#xff09;的缩写。这是一个由 NVIDIA 提供的库&#xff0c;用于加速深度学习应用程序。它包含了针对深度神经网络中常用操作&#xff08;如卷积、池化、归一化、激活层等&#xff0…

gcc tips - GCC使用技巧与高级特性

目录 1. 获取 GCC 编译器预定义的宏 2. 列出依赖的头文件 3. 保存预处理结果到文件&#xff08;展开define, 展开include header&#xff09; 4. 写回调跟踪记录函数运行 -finstrument-functions 5. -fdump-rtl-expand 画函数调用关系图 GCC&#xff0c;全称GNU Compiler …

内网环境下 - 安装linux命令、搭建docker以及安装镜像

一 内网环境安装docker 先在外网环境下载好docker二进制文件docker二进制文件下载&#xff0c;要下载对应硬件平台的文件&#xff0c;否则不兼容 如下载linux平台下的文件&#xff0c;直接访问这里即可linux版本docker二进制文件 这里下载docker-24.0.5.tgz 将下载好的文件…

LangChain+通义千问+AnalyticDB向量引擎保姆级教程

本文以构建AIGC落地应用ChatBot和构建AI Agent为例&#xff0c;从代码级别详细分享AI框架LangChain、阿里云通义大模型和AnalyticDB向量引擎的开发经验和最佳实践&#xff0c;给大家快速落地AIGC应用提供参考。 前言 通义模型具备的能力包括&#xff1a; 1.创作文字&#xf…

Oracle的错误信息帮助:Error Help

今天看手册时&#xff0c;发现上面有个提示&#xff1a; Error messages are now available in Error Help. 点击 View Error Help&#xff0c;显示如下&#xff0c;其实就是oerr命令的图形化版本&#xff1a; 点击Database Error Message Index&#xff0c;以下界面等同于命令…

Linux基础——进程初识(一)

1. 硬件 ①冯诺依曼体系 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。其详细结构如下图所示 在这里有几点要说明 1. 这里的储存器实际上指的是内存 2. 输入设备与输出设备都属于外设 常见的输入设备…

分布式搜索引擎(Elastic Search)+消息队列(RabbitMQ)部署

一、分布式搜索引擎&#xff1a;Elastic Search Elastic Search的目标就是实现搜索。是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。在数据量少的时候&#xff0c;我们可以通过索引去搜索关系型数据库中的数据&#xff0c;但是如果数…

【Linux下基本指令 —— 2】

Linux下基本指令 —— 2 十.more 指令语法&#xff1a;功能&#xff1a;常用选项&#xff1a;举例&#xff1a;Xshell7展示十一.less 指令语法&#xff1a;功能&#xff1a;选项&#xff1a;Xshell7展示 十二.head 指令语法&#xff1a;功能&#xff1a;选项&#xff1a;Xshell…

安卓1.0明显是基于linux内核开发的,安卓1.0是不是linux套壳?

安卓1.0明显是基于linux内核开发的&#xff0c;安卓1.0是不是linux套壳&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「安卓开发资料从专业入门到高级教程工具包」&#xff0c;点个关注&…