【数据结构】希尔排序

文章目录

  • 前言
  • 一、希尔排序的演示图例
  • 二、希尔排序:插入排序的优化版本
  • ☆三、核心算法思路
  • 四、算法思路步骤
    • (一)预排序 gap>1
    • (二)gap=1 插入排序 完成排序收尾
  • 五、码源详解
    • (1)ShellSort1 —— gap组 轮完一组再接下一组
    • (2)ShellSort2 —— 多组并排
  • 【关于gap幅度的选择】
  • 六、效率分析
    • (1)时间复杂度 O(N^ 1.3)(和O(N*logN)是一个量级的,可能O(N^ 1.3)会略差于O(N*logN))
    • 稳定性:不稳定



前言

前面我们学习了直接插入排序,我们可知道一个特点:当插排接近有序时,会非常的高效 。因此希尔研究出的希尔排序,令插排前面的数据更接近有序,就更高效。效率远超预期。


一、希尔排序的演示图例

在这里插入图片描述


二、希尔排序:插入排序的优化版本

希尔排序(Shell Sort)是一种排序算法,由美国计算机科学家Donald Shell于1959年提出。希尔排序是插入排序的一种改进版本Shell从插入排序中感悟到了插入排序只要保持 要调整待插入的数据的前面的数据越有序,排序的效率就越高旨在减少插入排序的交换操作和比较次数,从而提高排序效率。这个算法的名字是以发明者的名字命名的,虽然它也被称为 “递减增量排序”


☆三、核心算法思路

希尔排序法 又称 缩小增量法

  • 基本思路:
    先选定一个整数 gap(间距),把待排序文件中所有记录分成gap个组所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取gap=gap/2 或 gap=gap/3+1 ( 增量逐渐递减 ),重复上述分组和排序的工作。当gap到达=1时,所有记录在统一组内排好序(=插入排序)。

旨在通过 将间距为gap的数据之间的插入排序
将大的数据通过 间距gap的插入排序 更快的甩到后面去,小的数据甩到前面来
使待排序的序列在gap增量递减到1之前 [也就是在真正的插入排序之前] ,使数据更接近有序,让最后一次gap==1时的插入排序实现高效排序


四、算法思路步骤

(一)预排序 gap>1

  1. 分组排,间距为 gap 的数据都分为一组共有 gap 组(gap的间隔中,就是分好的组数)
  2. 间距为 gap 的数据都分为一组,每组数据进行单趟间距为gap的插入排序。(和插入排序的思路一样,但能让大的数据通过间距gap更快的甩到后面去,小的数据更快的到前面来)
  3. 增量递减 gap=gap/2 或 gap=gap/3+1(一般取这个) [ gap随n变化 ],进行多次预排序【gap递减的幅度也是有讲究的,请看下文 】
  4. gap越大,跳的越快,越不接近有序;gap越小越慢,越接近有序一点

多次预排的意义:让大的数据更快的到后面去,让小的数更快的到前面来。

(二)gap=1 插入排序 完成排序收尾

gap==1 时,就是插入排序。完成全部数据的排序。这时的数据已经非常接近有序了,这时的插入排序将会非常高效!


五、码源详解

(1)ShellSort1 —— gap组 轮完一组再接下一组

运用两层循环:

  1. 外层循环控制轮到的组号
  2. 内层循环控制一组内间隔为gap之间数据的比较
//希尔排序 —— 一组一组轮
void ShellSort(DataType* a, int n) {int gap = n;while (gap > 1) {//gap = gap / 2;gap = gap / 3 + 1;  //+1确保除到最后gap=1  //gap根据n的大小进行变化  //gap也能取n/2(最终除出来一定为1)//将 数组中 间隔为gap的数分为一组,共分为gap组,(间隔的数据中间的个数,便是所有已经分好的组数)//一组一组遍历for (int j = 0; j < gap; j++) {//每组进行遍历for (int i = j; i< n - gap; i += gap) {int end = i;int tmp = a[end + gap];            //先将要移的数先保存起来,前面发生挪动会将其覆盖while (end >= 0) {             //向前遍历完if (tmp < a[end]) {              //比tmp大就往后移a[end + gap] = a[end];end -= gap;                //再继续向前比较}else {break;}a[end + gap] = tmp;}}}}
}

注意:边界问题建议大家套值进去试,不容易出错。

(2)ShellSort2 —— 多组并排

一组排完第一个gap,就跳到另一组排另一组的第一个gap(每组都一部分一部分地排)

//希尔排序 —— 多组并排
void ShellSort(DataType* a, int n) {int gap = n;while (gap > 1) {//gap = gap / 2;      //gap也能取n/2(最终除出来一定为1)gap = gap / 3 + 1;    //+1确保除到最后gap=1  //gap根据n的大小进行变化  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;      //希尔排序的本质还是插入排序(end从前往后,每个end都向前遍历一次,遍历与其间隔gap的数),只不过是先通过gap分组来排出个相对有序,然后实现更高效的插入排序}else {break;}a[end + gap] = tmp;   //若发生了值的交换, end-=gap后 再加上gap 就是现在end的位置,就是将小的tmp值换到现在end的位置//若当前没有发生值的交换,则让tmp储存下一个相距gap的值 a[end+gap],让下一个值再进行 向前比较遍历}}}
}


【关于gap幅度的选择】

在这里插入图片描述《数据结构-用面相对象方法与C++描述》— 殷人昆

gap越大,跳的越快,越不接近有序,gap越大能让大和小数据更快地到两边。
gap越小越慢,越接近有序一点。

相比之下,gap=gap / 3 + 1 是比较适合的大小

int gap = n;while (gap > 1) {//gap = gap / 2;gap = gap / 3 + 1;  //+1确保除到最后gap=1  //gap根据n的大小进行变化  //gap也能取n/2(最终除出来一定为1)


六、效率分析

(1)时间复杂度 O(N^ 1.3)(和O(NlogN)是一个量级的,可能O(N^ 1.3)会略差于O(NlogN))

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

《数据结构(C语言版)》— 严蔚敏
在这里插入图片描述

《数据结构-用面相对象方法与C++描述》— 殷人昆
在这里插入图片描述
因为咋们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照O(n^1.25)到 O(1.6* n^1.25) 来算。

结论:O(N^ 1.3)
以 gap=N / 3 为例,有N/3组数据,每组里有3个数据进行比较,每组比3次 => 一个gap 比较(N/3 * 3 = N)次,但其中并不是都要交换 => N^2 => N^1.3 (N*logN).

稳定性:不稳定

呈如图这样的趋势:先增大,中间性能增大,再回复到小。

是个数学问题 " 复变函数 "
图中的变化参数 大概为 log2 N(底数为2)
在这里插入图片描述



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

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

相关文章

nodejs+vue高校实验室预约管理系统-计算机毕业设计

开发一款实验室预约管理系统&#xff0c;解决当前学校存在的实验室信息不透明&#xff0c;实验室空余时间不清晰&#xff0c;预约实验室过程繁琐费时的问题。提高实验室利用率。 对于学生&#xff0c; 系统应该分为实验室管理员模块和用户模块。实验室管理员模 块使用者为用户进…

从用户角度出发,探索低代码服务商选择本地部署模式的内在逻辑

随着越来越多的企业开始关注本地部署模式&#xff0c;很多低代码服务商也将视角转向了本地部署模式。毕竟只有将各种软件和程序部署在本地&#xff0c;才能够更好地保护企业的数据安全。但是&#xff0c;选择恰当的本地部署模式&#xff0c;对于服务商来说是一个非常复杂的过程…

React的useEvent 和 ahooks 的 useMemorizedFn 的深度分析和对比

父组件 const TestParent: React.FC<any> () > {const [State, setState] useState(0);const changeFun useCallback(() > {console.log(useCallback closure 里的 State, State);}, [State]);const changeFun_useEvent useEvent(() > {console.log(useEv…

Redis-命令操作Redis

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.Redis简介 1.1.什么是Redis Redis是一个开源&#xff08;BSD许可&#xff09;&#xff0c;内存存储的数据…

【Python3】【力扣题】219. 存在重复元素 II

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;哈希表。遍历每个元素&#xff0c;将元素及下标添加到字典&#xff0c;若当前元素已在字典中且下标之间距离k&#xff0c;则存在重复元素。 知识点&#xff1a;{}&#xff1a;创建空字典…

Express框架开发接口之前台分类导航

1.初始化 const handleDB require(../handleDB/index) // 获取全部导航 exports.allNav (req, res) > {(async function () {})() } // 更新或者添加导航 exports.upNav (req, res) > {(async function () {})() } // 根据id删除 exports.delNav (req, res) > {(a…

taro全局配置页面路由和tabBar页面跳转

有能力可以看官方文档&#xff1a;Taro 文档 页面路由配置&#xff0c;配置在app.config.ts里面的pages里&#xff1a; window用于设置小程序的状态栏、导航条、标题、窗口背景色&#xff0c;其配置项如下&#xff1a; tabBar配置&#xff1a;如果小程序是一个多 tab 应用&…

在基于亚马逊云科技的湖仓一体架构上构建数据血缘的探索和实践

背景介绍 随着大数据技术的进步&#xff0c;企业和组织越来越依赖数据驱动的决策。数据的质量、来源及其流动性因此显得非常关键。数据血缘分析为我们提供了一种追踪数据从起点到终点的方法&#xff0c;有助于理解数据如何被转换和消费&#xff0c;同时对数据治理和合规性起到关…

设计模式_状态模式

状态模式 介绍 设计模式定义案例问题堆积在哪里解决办法状态模式一个对象 状态可以发生改变 不同的状态又有不同的行为逻辑游戏角色 加载不同的技能 每个技能有不同的&#xff1a;攻击逻辑 攻击范围 动作等等1 状态很多 2 每个状态有自己的属性和逻辑每种状态单独写一个类 角色…

计算机毕业设计选题推荐-校园失物招领微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

界面控件DevExpress WinForms Gauge组件 - 实现更高级别数据可视化

DevExpress WinForms控件包含了超过150个随时可用的仪表盘预设&#xff0c;包括圆形&#xff0c;数字&#xff0c;线性和状态指示器等&#xff0c;来帮助用户实现更高级的数据可视化。 DevExpress WinForms有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业…

容器:软件性能测试的最佳环境

容器总体上提供了一种经济的和可扩展的方法来测试产品在实际情况下的性能&#xff0c;同时还能保持较低的资源成本和开销成本。 软件性能和可伸缩性是我们谈论应用程序开发时经常遇到的话题。一个很大的原因是应用程序的性能和可伸缩性直接影响其在市场上的成功。一个应用程序…