插入排序——希尔排序

插入排序——希尔排序

  • 7.5 插入排序——希尔排序
    • 概念和思路
    • 参考程序
    • 希尔排序的特性总结
      • 复杂度
      • 稳定性

7.5 插入排序——希尔排序

概念和思路

我们都知道,直接插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),但当数据无限接近有序或本身就是有序的时候,插入排序的时间复杂度能来到 O ( n ) O(n) O(n)

希尔排序就是诞生于这个背景下。

希尔排序法又称缩小增量法。希尔排序法的基本思想是:

先选定一个整数d,把待排序文件中所有记录分成若干个组,所有距离为d的记录分在同一组内,并对每一组内的记录进行排序。然后,取d的缩小值,再重复上述分组和排序的工作。当到达d=1时,所有记录在统一组内排好序

根据基本思想,得出希尔排序的思路:

  1. 预排序,使局部有序。
  2. 插入排序。

步骤:

  1. 数据间隔gap分为一组,总计分为gap组。
  2. 预排序:对gap组数据分别进行插入排序。
  3. 逐渐缩小gap的大小,当gap为1时,希尔排序和插入排序无任何差别。

gap的取法并不固定。从希尔排序这个排序算法被发明以来,人们一直都在寻找最合适的gap的取值。目前比较成熟的方法是,将gap和n绑定在一起。

推荐gap和n的关系为gap的初始值为n,之后gap通过表达式gap=f(gap)+1来缩小gap的大小,即 g a p < f ( g a p ) + 1 gap<f(gap)+1 gap<f(gap)+1f(gap)是数学表达式,一般常用 f ( g a p ) = g a p 2 f(gap)=\frac{gap}{2} f(gap)=2gap,或 f ( g a p ) = [ g a p / 3 ] f(gap)=[gap/3] f(gap)=[gap/3]。官方更喜欢用gap=gap/3+1,虽然它的预排序次数不如gap=gap/2

根据上述描述,画出草图:
请添加图片描述

参考程序

void shellSort(Datatype* a, int n) {int gap = n;while (gap > 1) {gap = gap / 2;//缩小增量int i = 0;for (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的表达式可以更换:

void shellSort(Datatype* a, int n) {int gap = n;while (gap > 1) {gap = gap / 3 + 1;//+1可以保证最后一次一定是1int i = 0;for (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;}}
}

希尔排序的特性总结

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

  2. gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,遍历就会很快。整体而言,对比插入排序可以达到可观的优化的效果。后续会进行对比。

复杂度

希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此目前给出的希尔排序的时间复杂度都不固定。

我们可以尝试计算这里写的希尔排序的时间复杂度。不想看的可直接翻到后面记结论。

void shellSort(Datatype* a, int n) {int gap = n;while (gap > 1) {gap = gap / 3 + 1;//+1可以保证最后一次一定是1int i = 0;for (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/3+1时,设x为for循环的次数,则 n = 3 x n=3^x n=3x(1是常数,对最后的时间复杂度没有影响),所以 x = l o g 3 n x=log_3n x=log3n

同理当gap=gap/2时可得 x = l o g 2 n x=log_2n x=log2n

之后gap组数据进行的预排序因为gap的大小会变,最后只能确认gap最大或最小时都是 O ( n ) O(n) O(n)

例如: g a p = n 3 gap=\frac{n}{3} gap=3n,每组3个,合计 n 3 \frac{n}{3} 3n组,每组最坏比较 ( 1 + 2 + 3 ) = 6 (1+2+3)=6 (1+2+3)=6次。

所以循环次数为 n 3 × 6 = 2 n \frac{n}{3}\times6=2n 3n×6=2n

g a p = = 1 gap==1 gap==1时,经过之前的预排序,数据已经十分有序,循环相当于检查一遍,所以这一步的时间复杂度为 O ( n ) O(n) O(n)

随着中间gap的变化运行次数也发生变化:
请添加图片描述

中间部分的时间复杂度(或者说循环次数)据说需要用复变函数和扎实的数学基础才能算出大概,以自己目前的数学功底无法算出希尔排序的复杂度,以后有机会的话再来看看。

算到这里,虽然我们无法算出,但可以借鉴别人的成果:

时间复杂度为 O ( n 1.3 ) O(n^{1.3}) O(n1.3),当 n → + ∞ n\rightarrow+\infty n+时,时间复杂度为 O ( n ( l o g 2 n ) 2 ) O(n(log_2n)^2) O(n(log2n)2)(按增量为 g a p = g a p / 3 + 1 gap=gap/3+1 gap=gap/3+1来算)。但就整体而言,记结论 O ( n 1.3 ) O(n^{1.3}) O(n1.3)会更轻松。

下面的介绍摘自《数据结构(C语言版)》,作者严蔚敏。
请添加图片描述

还有一段介绍摘自《数据结构-用面向对象方法与C++描述》,作者殷人昆
请添加图片描述

这里的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,如果继续用这个gap取值方式的希尔排序的话,暂时就按照时间复杂度在 O ( n 1.25 ) O(n^{1.25}) O(n1.25) O ( 1.6 n 1.26 ) O(1.6n^{1.26}) O(1.6n1.26)来算。

简记的话记时间复杂度 O ( n 1.3 ) O(n^{1.3}) O(n1.3)即可。

由于希尔排序并没有额外开辟空间,所以空间复杂度 O ( 1 ) O(1) O(1)

稳定性

希尔排序在不同的间隔(增量)序列下,相同元素的相对位置可能会改变。

例如,数组[4,4,1],假设gap==2,则第一次比较并产生交换的是第一个4和1,交换后数组变成[1,4,4],此时两个4的位置发生了变化。所以希尔排序是不稳定的排序算法

我们除了这个,还可以通过这个参考程序来证明。这里我们用结构体来同时存储数据和这个数据在原来的数组中的位置。对结构体数组排序后,可观察他们的顺序变化。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>typedef struct Datatype {int x; int i;
}Datatype;void shellSort(Datatype* a, int n) {int gap = n;while (gap > 1) {gap = gap / 3 + 1;//可以试着自己更换不同的增量int i = 0;for (i = 0; i < n - gap; ++i) {//插入排序int end = i;Datatype tmp = a[end + gap];while (end >= 0) {if (a[end].x > tmp.x) {a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;}}
}void f() {srand((size_t)time(0));Datatype a[30] = { 0 };int i = 0;for (i = 0; i < 30; i++) {a[i].x = rand() % 100 + 1;a[i].i = i;printf("%d %d,", a[i].x,a[i].i);}shellSort(a, 30);printf("\n");for (i = 0; i < 30; i++)printf("%d %d,", a[i].x, a[i].i);
}int main() {f();return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python小试牛刀:第一次爬虫,获取国家编码名称

使用场景&#xff1a; 需要初始化国家&#xff08;地区表&#xff09;&#xff0c;字段有国家名称、国家编码等等。 解决方案&#xff1a; 使用requests发送请求&#xff0c;使用bs4解析得到的HTML&#xff0c;打开F12&#xff0c;查看元素&#xff0c;&#xff08;可以Ctrl…

Java中的集合类与线程安全的讨论

1.ArrayList ArrayList是线程不安全的&#xff0c;可以在单线程中使用&#xff0c;在多线程中可以根据代码选择合适的时机进行加锁&#xff0c;实现线程安全的操作&#xff0c;但对代码能力要求较高。 2.Collections.synchronizedList(new ArrayList) 返回的List中的关键操作…

【数据结构】线性表——栈与队列

写在前面 栈和队列的关系与链表和顺序表的关系差不多&#xff0c;不存在谁替代谁&#xff0c;只有双剑合璧才能破敌万千~~&#x1f60e;&#x1f60e; 文章目录 写在前面一、栈1.1栈的概念及结构1.2、栈的实现1.2.1、栈的结构体定义1.2.2、栈的初始化栈1.2.3、入栈1.2.4、出栈…

A030-基于Spring boot的公司资产网站设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

企业生产环境-麒麟V10(ARM架构)操作系统部署kafka高可用集群

前言&#xff1a;Apache Kafka是一个分布式流处理平台&#xff0c;由LinkedIn开发并捐赠给Apache软件基金会。它主要用于构建实时数据流管道和流应用。Kafka具有高吞吐量、可扩展性和容错性的特点&#xff0c;适用于处理大量数据。 以下是Kafka的一些核心概念和特性&#xff1…

供应链管理、一件代发系统功能及源码分享 PHP+Mysql

随着电商行业的不断发展&#xff0c;传统的库存管理模式已经逐渐无法满足市场需求。越来越多的企业选择“一件代发”模式&#xff0c;即商家不需要自己储备商品库存&#xff0c;而是将订单直接转给供应商&#xff0c;由供应商直接进行发货。这种方式极大地降低了企业的运营成本…

机器学习 ---线性回归

目录 摘要&#xff1a; 一、简单线性回归与多元线性回归 1、简单线性回归 2、多元线性回归 3、残差 二、线性回归的正规方程解 1、线性回归训练流程 2、线性回归的正规方程解 &#xff08;1&#xff09;适用场景 &#xff08;2&#xff09;正规方程解的公式 三、衡量…

Unity类银河战士恶魔城学习总结(P127 Stat ToolTip属性提示)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了把鼠标放到属性上面就会显示属性的作用 UI_StatToolTip.cs 这段代码实现了一个UI提示框&#xff08;ToolTip&#xff09;功能…

C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 initializer_list表达式、tuple元组、pair对组再C日常还是比较常用的&#xff0c;尤其是对组在刷算法还是挺好用的&#xff0c;这里做一个简介&#xff1b;这三个语法结合C17的结构化绑定会更好用&#xff…

Python_爬虫1_Requests库入门

目录 Requests库 7个主要方法 Requests库的get()方法 Response对象的属性 爬取网页的通用代码框架 理解requests库的异常 HTTP协议及Requests库方法 HTTP协议 HTTP协议采用URL作为定位网络资源的标识。 HTTP协议对资源的操作 理解PATCH和PUT的区别 HTTP协议与Requse…

视频流媒体播放器EasyPlayer.js RTSP播放器视频颜色变灰色/渲染发绿的原因分析

EasyPlayer.js RTSP播放器属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;无须安装任何插件&#xff0c;起播快、延迟低、兼容性强&#xff0c;使用非常便捷。 EasyPlayer.js播放器不仅支持H.264与H.265视频编码格式&#xff0…

[JAVA]有关MyBatis环境配置介绍

什么是MyBatis环境配置&#xff1f; MyBatis是基于JDBC对数据库进行操作&#xff0c;在我们进行数据操作时&#xff0c;我们需要告诉MyBatis我们连接哪个数据库&#xff0c;ip地址&#xff0c;数据库名称&#xff0c;用户名密码等。以此来进行环境配置。 首先&#xff0c;MyB…

微搭低代码入门05循环

目录 1 for 循环2 while 循环3 do...while 循环4 break 语句5 循环展示组件总结 在编程中&#xff0c;循环是一种非常强大的控制结构&#xff0c;它允许我们重复执行一段代码直到满足某个条件为止。在微搭中&#xff0c;我们一般用循环来处理我们数据库返回的结果。 在微搭中&a…

11.13机器学习_线性回归

十 集成学习方法之随机森林 机器学习中有一种大类叫集成学习&#xff08;Ensemble Learning&#xff09;&#xff0c;集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。集成算法可以说从一方面验证了中国的一句老话&#xff1a;三个…

活动|华院计算作为联盟理事单位出席进博会全球人工智能合作论坛

第七届中国国际进口博览会&#xff08;进博会&#xff09;于11月5日至10日在上海举行&#xff0c;作为本次进博会的重要配套活动&#xff0c;首届人工智能全球合作论坛也于9日圆满落幕。本次论坛由全球招商中心委员会、人工智能全球合作论坛组委会主办&#xff0c;中国国际科技…

2、开发工具和环境搭建

万丈高楼平地起&#xff0c;学习C语言先从安装个软件工具开始吧。 1、C语言软件工具有两个作用 1、编辑器 -- 写代码的工具 2、编译器 -- 将代码翻译成机器代码0和1 接下来我们介绍两种C语言代码工具&#xff1a;devcpp 和 VS2019&#xff0c;大家可以根据自己的喜好安装。 dev…

【Qt实现虚拟键盘】

Qt实现虚拟键盘 &#x1f31f;项目分析&#x1f31f;实现方式&#x1f31f;开发流程 &#x1f31f;项目分析 需求&#xff1a;为Linux环境下提供可便捷使用的虚拟键盘OS环境&#xff1a;Windows 7/11、CentOS 7开发语言&#xff1a;Qt/C IDE&#xff1a;QtCreator 、Qt5.14.2功…

领夹麦克风哪个品牌好,手机领夹麦克风哪个牌子好,选购推荐

​无线麦克风凭借其无与伦比的便携性与灵活性&#xff0c;成为在演讲、表演以及会议等多种场合中不可或缺的有力帮手。它挣脱了线缆的束缚&#xff0c;使得声音的传播更加自由自在。其操作十分简便&#xff0c;只需简单配对就能投入使用&#xff0c;从而可以轻松地适应各类场景…

ADC输出码和输入电压转换关系

ADC输出码和输入电压转换关系 转换公式&#xff1a;ADC输出码(Vin / Vref) *2n 。其中Vin 是输入ADC芯片的电压&#xff0c;Vref是参考电压&#xff0c;n是ADC芯片的位数。 举个例子MS5182是一个16bit的ADC&#xff08;21665536&#xff09;&#xff0c;参考电压Vref4.096V&a…

IROS讲座:如何写出受欢迎的论文

讲座原名称&#xff1a;How to write papers people love reading 时间地点&#xff1a;2024年10月中旬&#xff0c;阿布扎比国家展览中心&#xff0c;阿联酋 演讲嘉宾照片&#xff1a; 以下是拍摄的部分PPT&#xff0c;并添加了中文笔记&#xff1a;