希尔排序(C++实现)

文章目录

  • 前言
  • 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、1while (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.xdnf.cn/news/148545.html

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

相关文章

进程调度的时机,切换与过程以及方式

1.进程调度的时机 进程调度&#xff08;低级调度〉&#xff0c;就是按照某种算法从就绪队列中选择一个进程为其分配处理机。 1.需要进行进程调度与切换的情况 1.当前运行的进程主动放弃处理机 进程正常终止运行过程中发生异常而终止进程主动请求阻塞&#xff08;如等待l/O)…

数据结构与算法-顺序表

数据结构与算法 &#x1f388;1.线性表&#x1f50e;1.1基本操作&#x1f50e;1.2线性表的存储结构 &#x1f388;2.线性表的顺序表示和实现&#x1f50e;2.1线性表的顺序存储表示&#x1f52d;2.1.1静态顺序表&#x1f52d;2.1.2动态顺序表 &#x1f50e;2.2顺序表基本操作的实…

MYSQL8解压版 windows 主从部署步骤及配置(包含配置文件,教程文件,免积分下载)

MYSQL8解压版 windows 主从部署步骤及配置 一.安装MSYQL 这里只讲大概,详细步骤、my.ini文件、安装包等会在页尾文件中(正常情况按首个mysql安装,只是名字有区别) 1.主库my.ini配置 [mysqld] #典型的值是5-6GB(8GB内存)&#xff0c;8-11GB(16GB内存), 20-25GB(32GB内存)&…

阿里云对象存储OSS SDK的使用

官方文档 https://help.aliyun.com/zh/oss/developer-reference/java 准备工作 windows安装好JDK&#xff0c;这里使用JDK1.8为例 windows安装好IDEA&#xff0c;这里使用IDEA2022 登录阿里云控制台&#xff0c;通过免费试用OSS或开通OSS 步骤 配置访问凭证 有临时和长期…

【Go】go-es统计接口被刷数和ip访问来源

go-es模块统计日志中接口被刷数和ip访问来源 以下是使用go的web框架gin作为后端&#xff0c;展示的统计页面 背景 上面的数据来自elk日志统计。因为elk通过kibana进行展示&#xff0c;但是kibana有一定学习成本且不太能满足定制化的需求&#xff0c;所以考虑用编程的方式…

mysql-binlog

1. 常用的binlog日志操作命令 1. 查看bin-log是否开启 show variables like log_%;2. 查看所有binlog日志列表 show master logs;3.查看master状态 show master status;4. 重置&#xff08;清空&#xff09;所有binlog日志 reset master;2. 查看binlog日志内容 1、使用mysqlb…

竞赛 机器视觉人体跌倒检测系统 - opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器视觉人体跌倒检测系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&…

腾讯云服务器完整建站过程(新手搭建网站教程)

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网分享使用腾讯云服务器建站教程&#xff0c;新手站长搭…

接口测试复习

一。基本概念 接口概念&#xff1a;系统与系统之间 数据交互的通道。 接⼝测试概念&#xff1a;校验 预期结果 与 实际结果 是否⼀致。 特征&#xff1a; 测试⻚⾯测试发现不了的问题。&#xff08;因为&#xff1a;接⼝测试 绕过前端界⾯。 &#xff09; 符合质量控制前移理…

ChainForge:衡量Prompt性能和模型稳健性的GUI工具包

ChainForge是一个用于构建评估逻辑来衡量模型选择&#xff0c;提示模板和执行生成过程的GUI工具包。ChainForge可以安装在本地&#xff0c;也可以从chrome浏览器运行。 ChainForge可以通过聊天节点对多个对话可以使用不同的llm并行运行。可以对聊天消息进行模板化&#xff0c;并…

ESLint自动修复代码规范错误

基于 vscode 插件 ESLint 高亮错误&#xff0c;并通过配置 自动 帮助我们修复错误 在设置中 settings.json添加这段代码就自动修复错误 // 当保存的时候&#xff0c;eslint自动帮我们修复错误 "editor.codeActionsOnSave": { "source.fixAll": true }, /…

GKR+Groth16:更快的MiMC证明

1. 引言 Consensys团队Alexandre Belling等人2022年论文 Recursion over Public-Coin Interactive Proof Systems; Faster Hash Verification 中&#xff0c;提出了&#xff1a; 用GKR来证明MiMC哈希计算的完整性将GKR verifier嵌入到SNARK&#xff08;Groth16&#xff09;电…

分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现PSO-CNN粒子群算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-CNN多特征分类预测&#xff0c;多特征输入模型&#xf…

java的内存模型(概念)

在java中&#xff0c;设计之初就有了&#xff1a;主内存、线程工作内存&#xff0c;所以其实每一个线程执行时&#xff0c;都是将主线程copy一份到工作线程&#xff0c;执行修改后&#xff0c;再同步回去。 所以&#xff0c;就有四组内存操作方式&#xff1a; 1、读主内存&…

Mysql以key-val存储、正常存储的区别

场景 你作为一个服务端工程师&#xff0c;假设产品要求设计这么一个页面&#xff0c;页面上包含很多模块&#xff0c;每个模块都可以单独进行变更&#xff0c;有些模块是富文本。 实现方式有很多&#xff0c;我们来聊比较常用的两种&#xff0c;看看mysql的表如何设计。 第一…

巧用@Conditional注解根据配置文件注入不同的bean对象

项目中使用了mq&#xff0c;kafka两种消息队列进行发送数据&#xff0c;为了避免硬编码&#xff0c;在项目中通过不同的配置文件自动识别具体消息队列策略。这里整理两种实施方案&#xff0c;仅供参考&#xff01; 方案一&#xff1a;创建一个工具类&#xff0c;然后根据配置文…

一个案例熟悉使用pytorch

文章目录 1. 完整模型的训练套路1.2 导入必要的包1.3 准备数据集1.3.1 使用公开数据集&#xff1a;1.3.2 获取训练集、测试集长度&#xff1a;1.3.3 利用 DataLoader来加载数据集 1.4 搭建神经网络1.4.1 测试搭建的模型1.4.2 创建用于训练的模型 1.5 定义损失函数和优化器1.6 使…

【Stm32-F407】Keil uVision5 的安装

文章内容如下&#xff1a; 1&#xff09;Keil uVision5 安装包的获取2&#xff09;Keil uVision5 的安装3&#xff09;Keil uVision5 中 Stm32-F407 芯片包的获取与安装4&#xff09;注册 Keil uVision5 1&#xff09;Keil uVision5 安装包的获取 Keil uVision5 安装包链接: h…

华硕平板k013me176cx线刷方法

1.下载adb刷机工具, 或者刷机精灵 2.下载刷机rom包 华硕asus k013 me176cx rom固件刷机包-CSDN博客 3.平板进入刷机界面 进入方法参考&#xff1a; ASUS (k013) ME176CX不进入系统恢复出厂设置的方法-CSDN博客 4.解压ME176C-CN-3_2_23_182.zip&#xff0c;把UL-K013-CN-3.2.…

java图书信息管理

一、项目概述 本图书信息管理系统旨在提供一个直观的用户界面&#xff0c;用于管理图书馆或书店的图书信息。系统包括图书添加、查询、借阅和归还等功能。 二、系统架构 系统采用JavaSwing作为前端UI框架&#xff0c;后端使用Java Servlet处理业务逻辑&#xff0c;数据存储在…