希尔排序(C++实现)

news2024/11/18 23:32:49

文章目录

  • 前言
  • 1. 基础概念
  • 2. 动图演示
  • 3. 代码实现
  • 4. 排序过程
  • 5. 效率分析
  • 6. 总结


前言

上篇文章讲了直接插入排序算法。

首先,在待排序的数组中,元素本身就是有序的情况下,就不需要移动任何元素,所以直接插入排序最好情况时间复杂度为 O ( n ) O(n) O(n)。但是,如果数组中元素多数都是有序(基本有序)的情况下,那么需要移动的元素数量就会大大减少。

这里所谈到的基本有序,可以理解成小的关键字大部分在前面,大的关键字大部分在后面,比如如下数组中的元素 {1,2,10,16,18,45,23,99,42,67}{16,1,45,23,99,2,18,67,42,10} 数组中元素相比,前者算得上是基本有序了。

其次,如果数组中元素数量较少,那么直接插入排序的效率也会很高。基于上述两点对直接插入排序算法进行改进,就可以得到希尔排序算法。

1. 基础概念

希尔排序这个名字来源于它的发明者希尔(Donald Shell),这类排序也被称作 “缩小增量排序(Diminishing Increment Sort)”,是直接插入排序的一种更高效率的改进版。

希尔排序算法的思想是先追求元素的部分有序,然后再逐渐逼近全局有序。具体的做法是先将整个待排序记录序列(或称为数组元素)分割成若干个子序列,分别进行直接插入排序,等到整个序列中的记录基本有序时,再对所有记录进行一次直接插入排序。

希尔排序会进行多趟排序,每趟排序会设置一个增量,这里注意,这个增量初始值到底是多大才合适并没有公论,比如可以将 “数组中元素数量 / 2" 作为增量的初始值。

这里以数组 {1,2,10,16,18,45,23,99,42,67} 为例,来说明希尔排序。

(1)首先,数组中的元素有 10 个,所以增量初值可以设置为 5。第一趟排序,把距离为 5 的元素划分到一个子序列中,并对这个子序列中的元素从小到大排序。如下图所示:

在这里插入图片描述

从上图可以看到,第一趟排序,因为增量值是 5,这意味着即将排序的元素间隔 5 个位置。所以下标为 0 的元素 16 和下标为 5 的元素 2 需要进行大小比较,并根据从小到大的顺序决定谁在前谁在后。因为 2 比 16 小,所以 2 应该在前,也就是 16 和 2 互换位置。接着,有元素 1 和 18、元素 45 和 67、元素 23 和 42、元素 99 和 10 依次进行大小比较并排序,本趟排序得到的结果数组元素值为 {2,1,45,23,10,16,18,67,42,99}

(2)第二趟排序,要缩小增量的值,比如可以每次缩小一半(希尔本人这样建议),也就是:增量 = 增量 / 2,原来增量的值是 5,这次增量的值就变成了 2,即把距离为 2 的元素划分到一个子序列中并对该子序列中的元素从小到大排序。如下图所示:

在这里插入图片描述

从上图可以看到,第二趟排序因为增量值是 2,意味着即将排序的元素间隔 2 个位置。所以下标为 0 的元素 2、下标为 2 的元素 45、下标为 4 的元素 10、下标为 6 的元素 18、下标为 8 的元素 42 进行大小比较并根据从小到大的顺序决定谁在前谁在后。最终得到 {2、10、18、42、45} 的顺序。

同理,元素 {1、23、16、67、99} 从小到大排序,本趟排序得到的结果数组元素值为 {2,1,10,16,18,23,42,67,45,99}。可以看到,每一趟排序,都使数组中的元素更进一步基本有序。

(3)第三趟排序,继续缩小增量的值,增量 = 增量 / 2,原来增量的值是 2,这次增量的值就变成了 1。这就意味着要对数组中的所有元素进行从小到大的直接插入排序,增量值为 1 后的排序也是最后一次排序。最终数组元素的值就变成了 {1,2,10,16,18,23,42,45,67,99}

所以,一共进行了三趟排序,得到了最终排好序的数组元素。如下图所示:

在这里插入图片描述

2. 动图演示

可以看下面的动图演示结果:

在这里插入图片描述

3. 代码实现

代码如下:

#include <iostream>
using namespace std;

template<typename T>
void ShellSort(T arr[], int len)
{
	if (len <= 1) //不超过1个元素的数组,没必要排序
		return; 
		
	int gap = len / 2; //gap: 增量,取值分别为7、3、1
	while (gap >= 1)
	{
		//每一趟采用直接插入排序来实现(实现代码与直接插入排序其实是一摸一样) 
		//第一次while循环:i=7~14;第二次while循环:i=3~14;第三次while循环i=1~14;
		
		for (int i = gap; i < len; ++i) //i值每次改变可以处理到不同的子序列
		{
			if (arr[i] < arr[i - gap])
			{
				T temp = arr[i]; ;//暂存arr[i]值,防止后续移动元素时值被覆盖
				int j;
				for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap) //检查所有前面排好序的元素
				{
					arr[j + gap] = arr[j]; //所有大于temp的元素都向后移动
				}
				arr[j + gap] = temp; //复制数据到插入位置,注意j因为被减了gap,这里加回来
			}
		}
		
		cout << "本趟希尔排序,增量为: " << gap <<" "<<"结果为: ";
		for (int i = 0; i < len; ++i)
			cout << arr[i] << " ";
		cout << endl;
		
		gap /= 2; //增量每次减半	 
	}
	
	return;
}

在主函数中,加入一个包含 15 个元素的数组,然后进行从小到大排序。

int main()
{
	int arr[] = { 67, 1, 45, 23, 99, 2, 18, 16, 42, 10, 8, 44, 106, 29, 4};
	int len = sizeof(arr) / sizeof(arr[0]);
	
	cout <<"原始数据为:"; 
	for (int i = 0; i < len; ++i) 
		cout << arr[i] << " ";
	cout << endl;
	
	ShellSort(arr, len); 
	
	cout << "希尔排序后的数据为:"; 
	for (int i = 0; i < len; ++i) 
		cout << arr[i] << " ";
	cout << endl;
	return 0; //我低洼地 
} 

排序结果如下:

在这里插入图片描述

从结果可以看到,一共进行了三趟排序,增量值分别为 7、3、1。

4. 排序过程

希尔排序算法的代码实现并不复杂,但可能理解起来不那么直观,这里阐述一下程序的执行步骤。

当增量值为 7 时,程序是怎么工作的呢?如下图所示:

在这里插入图片描述

上图展示的是第一趟排序(增量为 7)的程序执行流程,为了清晰,分成了几个子图来绘制。最上面是待排序的原始数组数据。排序时先看子图 1:

  • 67 和 16 交换位置。
  • 1 和 42 不需要交换位置。
  • 45 和 10 交换位置。
  • 23 和 8 交换位置。
  • 99 和 44 交换位置。
  • 2 和 106 不需要交换位置。
  • 18 和 29 不需要交换位置。

这里值得说的是元素 4:

  • 元素 4 先和 67 交换位置,如子图 2。
  • 元素 16 再和 4 交换位置,如子图 3。
  • 子图 2 和子图 3 这两次连续的交换位置是通过代码中最内层 for 循环实现的。

所以,经过第一趟排序后,数组中的元素内容就是:{4, 1, 10, 8, 44, 2, 18, 16, 42, 45, 23, 99, 106, 29, 67}

接着,增量值减少为原来的一半,从 7 变为了 3,开始第二趟排序,增量值为 3 时,程序是怎么工作的呢?如下图所示:

在这里插入图片描述

其中:

  • 4 和 8 不需要交换位置。
  • 1 和 44 不需要交换位置。
  • 10 和 2 交换位置。
  • 8 和 18 不需要交换位置。
  • 44 和 16 交换位置。
  • 10 和 42 不需要交换位置。
  • 18 和 45 不需要交换位置。
  • 44 和 23 交换位置,如子图 2。
  • 42 和 99 不需要交换位置。
  • 45 和 106 不需要交换位置。
  • 44 和 29 交换位置,如子图 3。
  • 99 和 67 交换位置。

所以,经过第二趟排序后,数组中的元素内容就是:{4, 1, 2, 8, 16, 10, 18, 23, 42, 45}

接着,增量值减少为原来的一半,从 3 变为了 1,开始第三趟排序,此时,程序的执行步骤与前面讲述的直接插入排序步骤完全一致,这里就不再赘述,经过了三趟排序,数组中的元素已经按照从小到的顺序排好了,数组中元素的最终内容就是:{1, 2, 4, 8, 10, 16, 18, 23, 29, 42, 41, 45, 67, 99, 106}

5. 效率分析

从代码中可以看到,希尔排序实现代码的空间复杂度为 O ( 1 ) O(1) O(1)。而对于时间复杂度的分析,本算法则显得比较复杂。当采用不同的增量序列,比如上面代码中每次增量减少为原来的一半时,希尔排序的总趟数会不同,而且每趟排序元素对比次数和元素移动次数都可能会受到影响。所以希尔排序的时间复杂度目前为止还无法用数学手段确切地证明。

但如果增量的初值直接设置为 1 的话,那么希尔排序会退化为直接插入排序,这时的时间复杂度是希尔排序的最坏时间复杂度即 O ( n 2 ) O(n^2) O(n2)。如果待排序元素的数量在一定的范围内,那么时间复杂度可以达到 O ( n 1.3 ) O(n^{1.3}) O(n1.3),平均时间复杂度为 O ( n l o g 2 n ​ ) O(nlog^n_2​) O(nlog2n),这意味着希尔排序算法优于直接插入排序算法。

此外,希尔排序算法是不稳定的,这不难证明。试想一下具有 3 个元素 99、10、10 的数组,因为增量值的设定并没有公论,所以如果设定第一趟排序增量值为 2,第二趟排序增量值为 1,那么后面两个都是 10 的数组元素位置就会发生改变。如下图所示:

在这里插入图片描述

上图所示,第一趟排序元素 99 和最末尾的元素 10 进行了位置交换,而第二趟排序并没有做任何数组元素位置的交换。但显而易见,原下标为 2 的数组元素 10 被移动到了下标为 0 的位置,跑到了下标为 1 的数组元素 10 之前。所以,希尔排序算法是不稳定的。

6. 总结

希尔排序算法是对直接插入排序算法的改进,它先追求元素的部分有序,然后再逐渐逼近全局有序。

希尔排序通过进行多趟排序的方式来达成,排序开始时会选择一个增量,根据增量将待排序序列分成若干个子序列,对每个子序列进行直接插入排序。然后逐步缩小增量并继续根据增量将待排序序列分成若干个子序列并对每个子序列进行直接插入排序,直到增量变为了 1 才完成了整个希尔排序的过程。

与直接插入排序相比,希尔排序的速度更快。同时,通过对增量的调整也许能进一步加速排序效率。不过,希尔排序的实现代码更加复杂,且选择合适的增量也显得特别重要。此外还要注意,希尔排序算法是不稳定的。

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

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

相关文章

Javascript 事件的动态绑定

动态绑定事件&#xff0c;是指在代码执行过程中&#xff0c;通过Javascript代码来绑定事件。这种技术可以大大增强网页的交互性和用户体验。上一期介绍的是通过事件监听器 EventListener 去实现元素颜色的变化。这一期将通过动态绑定方法去实现&#xff0c;对象.事件 匿名函数…

哈希/散列--哈希表[思想到结构]

文章目录 1.何为哈希?1.1百度搜索1.2自身理解1.3哈希方法/散列方法1.4哈希冲突/哈希碰撞1.5如何解决?哈希函数的设计 2.闭散列和开散列2.1闭散列/开放定址法2.2开散列/链地址法/开链法1.概念2.容量问题 3.代码实现[配备详细注释]3.1闭散列3.2开散列 1.何为哈希? 1.1百度搜索…

【工具】idea 设置自动渲染注释

前言 需求&#xff1a;自动渲染文档注释&#xff0c;看源码更加舒服。 已知 crtl alt Q 可以 设置 尝试搜索 render&#xff0c;发现有启用 “渲染文档注释” 的地方 坐标 &#xff1a; Settings -> Editor-> Appearance

CSS3与HTML5

box-sizing content-box&#xff1a;默认&#xff0c;宽高包不含边框和内边距 border-box&#xff1a;也叫怪异盒子&#xff0c;宽高包含边框和内边距 动画&#xff1a;移动translate&#xff0c;旋转、transform等等 走马灯&#xff1a;利用动画实现animation&#xff1a;from…

分布式锁:jvm本地加锁解决商品超卖的方案

一 分布式锁 1.1 分布式锁的作用 在多线程高并发场景下&#xff0c;为了保证资源的线程安全问题&#xff0c;jdk为我们提供了synchronized关键字和ReentrantLock可重入锁&#xff0c;但是它们只能保证一个工程内的线程安全。在分布式集群、微服务、云原生横行的当下&#xff…

python二次开发CATIA:根据已知数据点创建曲线

已知数据点存于Coords.txt文件如下&#xff1a; 8.67155477658819,20.4471021292557,0 41.2016126836927,20.4471021292557,0 15.9568941320569,-2.93388599177698,0 42.2181532110364,-6.15301746150354,0 43.0652906622083,-26.4843096139083,0 -31.6617679595947,-131.1513…

Java基本数据类型和变量

目录 一、基本数据类型 1.1 整型 1.1.1 byte 1.1.2 short 1.1.3 int 1.1.4 long 1.2 浮点型 1.2.1 float 1.2.2 double 1.3 字符型 1.4 布尔型 二、变量 2.1 变量的概念 2.2 语法格式 2.3 整型变量 2.3.1 整型变量 2.3.2 长整型变量 2.3.3 短整型变量 2.3.…

【Unity2022】Unity实现在两个物体之间连出一条线

文章目录 Line Renderer组件添加Line Renderer组件重要属性Positions&#xff08;位置&#xff09;Width &#xff08;宽度&#xff09;Material&#xff08;材质&#xff09;其他属性 使用脚本绘制直线绳子运行结果其他文章 Line Renderer组件 我们可以使用LineRenderer组件来…

【GO 编程语言】面向对象

指针与结构体 文章目录 指针与结构体一、OOP 思想二、继承三、方法 一、OOP 思想 Go语言不是面向对象的语言&#xff0c;这里只是通过一些方法来模拟面向对象&#xff0c;从而更好的来理解面向对象思想 面向过程的思维模式 1.面向过程的思维模式是简单的线性思维&#xff0c;…

苹果电脑壁纸软件Irvue for mac激活

Irvue是一款Mac上的壁纸软件&#xff0c;里面包含了数千张来精彩照片&#xff0c;方便用户将喜欢的照片设置为壁纸。以下是Irvue软件的一些主要特点和功能&#xff1a; 丰富的壁纸资源&#xff1a;Irvue提供了数千张来自Unsplash的高分辨率照片&#xff0c;涵盖了风景、建筑、…

【前段基础入门之】=>元素定位布局

导语&#xff1a; CSS 元素定位&#xff0c;是目前 CSS 页面布局的一种主要方式。 文章目录 相对定位开启相对定位相对定位的参考点相对定位的特点 绝对定位开启绝对定位绝对定位的参考点绝对定位的特点 固定定位开启固定定位固定定位的参考点固定位的特点 粘性定位开启粘性定位…

详解C语言—编译与链接

目录 1、程序的翻译环境 2、C语言程序的编译链接 3、程序执行的过程&#xff1a; 1、程序的翻译环境 在ANSI C标准的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境&#xff0…

基于SSM的电动车上牌管理系统(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的电动车上牌管理系统&#xff08;有报告&#xff09;。Javaee项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringM…

【3】c++设计模式——>UML表示类之间的关联关系

关联关系 关联&#xff08;Assocition&#xff09;关系是类与类之间最常见的一种关系&#xff0c;它是一种结构化的关系&#xff0c;表示一个对象与另一个对象之间有联系&#xff0c;如汽车和轮胎、师傅和徒弟、班级和学生等。在UML类图中&#xff0c;用&#xff08;带接头或不…

JVM篇---第一篇

系列文章目录 文章目录 系列文章目录一、知识点汇总二、知识点详解:三、说说类加载与卸载一、知识点汇总 JVM是Java运行基础,面试时一定会遇到JVM的有关问题,内容相对集中,但对只是深度要求较高. 其中内存模型,类加载机制,GC是重点方面.性能调优部分更偏向应用,重点突出实践…

专业图标制作软件 Image2icon 最新中文 for mac

Image2Icon是一款用于Mac操作系统的图标转换工具。它允许用户将常见的图像文件&#xff08;如PNG、JPEG、GIF等&#xff09;转换为图标文件&#xff08;.ico格式&#xff09;&#xff0c;以便在Mac上用作应用程序、文件夹或驱动器的自定义图标。 以下是Image2Icon的一些主要功…

基于vc6+sdk51开发简易文字识别转语音的程序

系统&#xff1a;window7 软件&#xff1a;vc6.0 目的&#xff1a;简易文字转语音真人发声 利用2023国庆小长假&#xff0c;研究如何将文言转语音&#xff0c;之前在网上查询相关知识&#xff0c;大致了解微信语音转换&#xff0c;翻译官之类软件的原理&#xff0c;但要加入神…

python二次开发CATIA:旋转楼梯

旋转楼梯&#xff0c;也称为螺旋形或螺旋式楼梯&#xff0c;是一种围绕单柱或中心轴旋转而上的楼梯类型。由于其流线造型美观、典雅&#xff0c;并且能够节省空间&#xff0c;因此受到很多人的喜爱。这种楼梯最早可以追溯到公元前1000年左右&#xff0c;当时在所罗门王的宫殿中…

【4】c++设计模式——>UML表示类之间的聚合关系

聚合关系表示整体与部分的关系&#xff0c;在聚合关系中&#xff0c;成员对象时整体的一部分&#xff0c;但是成员对象可以脱离整体对象独立存在&#xff0c;当整体被析构销毁的时候&#xff0c;组成整体的这些子对象是不会被销毁的&#xff0c;是可以继续存活&#xff0c;并在…