八大算法排序@希尔排序(C语言版本)

目录

  • 希尔排序
    • 概念
    • 算法思想
      • 示例
      • 分析
      • 结论
      • 算法步骤
        • 选择增量序列
        • 按增量分组
        • 逐步缩小增量
      • 算法优势
    • 代码实现
      • 核心算法
      • 希尔排序代码实现:
    • 时间复杂度
    • 空间复杂度
    • 特性总结



该排序会关联到直接插入排序的知识点,如果对于直接插入排序还有所疑惑,可以跳转文章过去观摩一二,希望能够帮助到你。

希尔排序

概念

  希尔排序(Shell Sort)是一种基于直接插入排序的排序算法,又称缩小增量法。其主要思想是通过对数据集合进行多次的直接插入排序,每次使用不同的增量进行直接插入排序,最终使数据基本有序,最后进行一次直接插入排序,达成排序的效果。




算法思想

希尔排序是对直接插入排序的优化算法

示例

让我们回忆一下直接插入排序的特点,下面有两个数组,
数组arr1:
数组1

数组arr2:
数组2

现在使用直接插入排序,分别对数组 arr1 和 arr2 进行升序的排序,达到以下的效果:
排序结果


分析

  对于数组arr1而言,插入的第一个元素,是数组中最大的元素9。那么对于后续要插入的元素,都要进行的一个步骤是与元素9进行交换。根据最终完成的排序结果,元素9是要排到最后的,即元素9要移动八次才能到达最终的位置。
  对于数组arr2而言,插入的第一个元素是3,根据排序完成的最终结果,我们发现只需要对元素3移动两次,就能到达最终的位置。到了最终的位置后,该元素便扎根不移动了。


结论

根据两个数组中的第一个元素插入,在整个排序中的情况。我们能够发现:
  要实现升序的排序时,对于直接插入排序而言,“ 数组中较大的元素如果较靠前 ” 的情况,比起 “ 数组中较小的元素较靠前 ” 的情况移动的次数更多,即复杂度更高。这也吻合直接插入排序的特点,如果原数组大体的趋势上越接近我们要实现的排序的效果,那么直接插入排序的效率/时间复杂度相对较低( 极端情况下,数组已经有序,那么时间复杂度为O(N) );如果原数组大体的趋势上与我们要实现的排序的效果相背而驰,那么直接插入排序的效率/时间复杂度相对更高( 极端情况下,数组是逆序,那么时间复杂度为O(N^2) )。因此直接插入排序的时间复杂度为O(N^2)。


了解至此,让我们再次解读 “ 希尔排序是对直接插入排序的优化算法 ” 这句话的意思。希尔排序,就是进行多次的直接插入排序,每次使用不同的增量,达到让数组大体上逐渐的趋近有序且是要实现的排序的效果。最后一次使用直接插入排序的算法时,此时因为数组大体上已经趋近于最终的排序效果,所以对于整个数组的元素的移动并不多,因此效率上更高。整体上相对于直接插入排序的效率而言,得到了提高。


算法步骤

  在阐述算法步骤前,让我们想这么一个问题:希尔排序是一种多次排序的算法,且每一次的排序都是直接插入排序。那么每一次排序的区别是什么?
带着这个问题,我们对上述的数组arr1进行图文模拟希尔排序的过程。


第一次直接插入排序,选择间距 (下标 ) 等于5的两两元素进行排序。如下图:
在这里插入图片描述
元素9和元素1,下标间距为5,而距离元素1间距为5的已经超出数据的范围。其他的元素同理,即数组中每个元素有且只有一个与自己距离为5的元素(除了元素5之外)。因此间距为5时,具体的直接插入排序,实现的是:
元素9和元素1的升序排序,元素9和元素1位置互换、
元素6和元素8的升序排序,元素6和元素8位置不变、
元素7和元素2的升序排序,元素7和元素2位置互选、
元素3和元素4的升序排序,元素3和元素4位置不变、
元素5单独一个,则不移动,在原位置上。

也可以这么理解:元素之间间距相等(等于5)且是“ 邻居 ”关系的,划分为一个数组。在原先各自的位置上,对数组的元素进行排序。实现在这些位置上的数组元素是升序/降序的效果。


第二次直接插入排序,选择间距 (下标 ) 等于4的两两元素进行排序。如下图:

在这里插入图片描述

元素1、5 和元素5、4,两两元素之间间距为4,元素1、5、4组成一个小数组,元素5是元素1的“邻居”,元素4是元素5的 “邻居”,对这个小数组进行升序/降序的排序。如升序排序时,原先的顺序1、5、4,将变为1、4、5。但是位置依旧是原先的那三个位置,只是在这三个位置上的元素达成了升序的排布效果。
而元素6间距为4的只有元素9的位置符合,而距离元素9间距为4的已经超出数组的范围。因此对于元素6切分的小数组中,只有元素6和元素9这两个数据,其他同理。分别对各个切分出来的小数组进行排序,最终达到整体上趋近于想要的排序效果,如升序时,大的元素靠后。
注意:实际排序时,要想想直接插入排序是如何实现的。

同样的思路,我们对间距分别为3、2、1的进行演变模拟:


第三次直接插入排序,选择间距 (下标 ) 等于3的两两元素进行排序。如下图:

在这里插入图片描述



第四次直接插入排序,选择间距 (下标 ) 等于2的两两元素进行排序。如下图:

在这里插入图片描述



最后一次直接插入排序,选择间距 (下标 ) 等于1的两两元素进行排序。如下图:
在这里插入图片描述


  以上,通过不同的间距,进行总共5次的直接插入排序,最终达成了数组arr1升序的排序效果,整个过程的实现便是希尔排序的算法逻辑了。其中所谓的间距就是前文提到的增量
  我们发现随着不同的增量,执行直接插入排序后,数组在整体上逐渐的形成了升序的效果(较大的数靠后,较小的数靠前)。当增量等于1的时候,排序的想法和直接插入排序的想法一致。


结合以上的学习,下面给出希尔排序的步骤总结:

选择增量序列

选择一个增量序列,这个序列的选择对排序的效率有影响。常用的增量序列有希尔建议的序列(例如,n/2、n/4、n/8…直到增量为1)。


注释:
  n为数组的个数,如上述对n=9的数组arr1模拟中。选择的增量序列为5、4、3、2、1。细心的同学在观察上述模拟过程图中可以发现,有些间距的直接插入排序,对数组的变动并不大。比如间距为4的时候,只是对元素5和元素4进行了交换,其他的并没有变动。对于这种间距的直接插入排序,效果不大,但是却是实打实造成一定效率的消耗的,我们可以排除掉。
  结论:不同的增量序列,对排序的效率是有影响的。如何选取一个高效的增量序列,这牵扯到数学问题。根据前人的计算,选择“ n/2、n/4、n/8…1 ”序列或者“ n/3、n/9、…1 ”序列时,效率最理想。
注:要确保增量为1的直接插入排序存在。



按增量分组

将待排序的元素按照增量分成若干个子序列,对每个子序列进行插入排序。这样,每个子序列都是部分有序的。


注释:
  如上述间距为2时,将数组 arr1 切分为两个子序列,分别是 { 1 , 2 , 5 , 8 ,4 } 、{ 6 , 3 , 9 , 7 }。然后分别对这两个子序列进行插入排序,最终在数组 arr1 中,达成了各个子序列所在的位置是有序的。arr1 数组整体上也趋近于有序。



逐步缩小增量

重复上述步骤,逐步减小增量,直至增量为1。当增量为1时,整个序列基本有序,再进行一次直接插入排序,排序完成。


注释:
  如上述间距从5、4、3、2逐步减小时,数组 arr1 整体上已经愈发的有序。当间距/增量减小为1时,整个数组基本是有序的状态,这是进行直接插入排序,完成最终的排序效果的同时也大大的增大了排序的效率。



算法优势

希尔排序的优势在于,它可以在开始时快速地将较小的元素移动到合适的位置,从而减小后续插入排序的工作量。这样,希尔排序相较于直接插入排序在效率上有所提高,特别是对于较大数据集合。虽然希尔排序不如一些更现代的排序算法,如快速排序或归并排序,但在某些特定场景下仍然有其优势。



  简洁而言,希尔排序就是对数组进行切分,隔开间距的排序、让数组整体上接近有序。再每次缩短间距,一次次的下来,数组整体上已经临近与有序的状态。当间距等于1时,排序的想法和直接插入排序的想法一致。
下面是代码的实现。


代码实现

核心算法

// 交换数值函数
void swap(int* x1,int* x2)
{int tmp=*x1;*x1=*x2;*x2=tmp;
}// 直接插入排序
void InsertSort(int* a, int n)
{assert(a);// 最后一个 n-1(下标) 插入到 前n-2个排好序列的数 // i是有序序列的下标,当i=n-1,即数组最后一个数的下标时,则整个数组就是有序的序列了for (int i = 0; i < n - 1; i++){// 单趟排序,设定end为已排序部分的最后一个元素下标int end = i;	// 有序序列的最后一个下标是iint tmp = a[end + 1];  // a[end+1] 是即将要插入的数据while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;	}// 出了for循环,利用直接插入算法实现对数组的升序/降序的效果
}



希尔排序代码实现:

// 交换数值函数
void swap(int* x1,int* x2)
{int tmp=*x1;*x1=*x2;*x2=tmp;
}// 希尔排序 : 时间复杂度O(N^1.3 - N^2)
void ShellSort(int* a, int n)
{// 1、gap>1相当于预算排序,让数组接近有序// 2、gap == 1就相当于直接插入排序,保证有序int gap = n;while (gap > 1){gap = gap / 3 + 1;//增量gap,+1保证了最后一次gap一定是1//gap == 1 最后一次就相当于直接插入排序// 直接插入排序for (int i = 0; i < n - gap; i++){int end = i;	// 有序数组的最后一个元素的下标int tmp = a[end + gap];	// 要插入的数据,与要排序的间距为gapwhile (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}}

  观察以上代码,与直接插入排序的代码实现比对发现,希尔排序在直接插入排序外面多了一层循环,用来确保每次的直接插入排序的增量不同。而希尔排序循环内的直接插入排序与直接插入排序的区别就在于元素之间、边界的差异。具体的可以根据代码自己在图纸上结合上述演示的演示图带入数据过一遍。





时间复杂度

希尔排序的时间复杂度介于N^1.3 - N^2 之间,即O(N^1.3 - N^2)。具体的计算过程就不加以解释了,有兴趣的可以自行在网上了解、


空间复杂度

O(1)。


特性总结

1、 希尔排序是对直接插入排序的优化;
2.、当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的
了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的
对比;
3.、希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3—
N^2);
4.、稳定性:不稳定。

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

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

相关文章

数据结构学习 jz42连续子数组最大和

关键词&#xff1a;动态规划 滚动数组 最长上升子序列 这道题比较简单&#xff0c;类似最长上升子序列&#xff0c;比最长上升子序列简单。 和最长上升子序列的区别&#xff1a;这道题因为是连续的&#xff0c;所以只用记录max就好了。最长上升子序列是不连续的&#xff0c;所…

FA发放云桌面并与FC对接

&#xff08;7&#xff09;分配桌面&#xff08;该组为刚刚创建的域名用户和组&#xff09;&#xff0c;确认无误&#xff0c;直接发放 &#xff08;8&#xff09;可在任务中心查看发放的进度 3、FA的登录流程 &#xff08;1&#xff09;登录WI&#xff1a;客户端访问VLB&…

网络安全B模块(笔记详解)- 数字取证

数据分析数字取证-attack 1.使用Wireshark查看并分析Windows 7桌面下的attack.pcapng数据包文件,通过分析数据包attack.pcapng找出恶意用户的IP地址,并将恶意用户的IP地址作为Flag(形式:[IP地址])提交; 解析:http.request.method==POST ​ Flag:[172.16.1.102] 2.继续…

国图公考:公务员政审阶段,这四类家庭不予通过!

公务员考试需要经过笔试、面试、体检、政审等步骤&#xff0c;全部合格之后才能成为一名公务员&#xff0c;其中政审的要求是非常严格的&#xff0c;如果你是以下四类家庭的孩子&#xff0c;政审是不通过的。 1. 配偶、直系亲属和三代以内旁系亲属因参与民族、宗教、非法宗教、…

vue3项目创建

安装node.js vue --version &#xff08;4.5.0以上&#xff09; npm install -g vue/cli vue create 项目名称 npm run dev 启动 npm run build 打包 ———————— vite 创建工程 npm create vuelatest npm i npm run dev 启动 npm run build 打包 项目结构…

wordpress在界面将站点地址直接修改为https导致上不去问题的解决办法

wordpress在界面将站点地址直接修改为https导致上不去问题的解决办法 #修改数据库yz_options

理解UML中的依赖关系

理解UML中的依赖关系 在面向对象的设计中&#xff0c;理解各种类之间的关系对于构建一个清晰、可维护的系统至关重要。UML&#xff08;统一建模语言&#xff09;为我们提供了一种可视化这些关系的方式。今天&#xff0c;我们将深入探讨UML中的依赖关系&#xff08;Dependency&a…

中科驭数鄢贵海新年演讲:数字经济下的算力基础先行,DPU自主创新力量大有可为

近日&#xff0c;中科驭数创始人、CEO鄢贵海受邀在北京电视台《金融街午餐会》新年特别活动中发表新年演讲。 鄢贵海在新年演讲中提到&#xff0c;在21世纪头30年&#xff0c;我们不可思议地经历了三次重要的科技变革&#xff0c;分别是互联网的普及、移动互联网的崛起、以及人…

一种可扩展的多属性可控文本生成即插即用方法

《An extensible plug-and-play method for multi-aspect controllable text generation》论文解读 文章的主要工作 &#xff08;1&#xff09;提出了一种可扩展的即插即用方法&#xff0c;PROMPT GATING&#xff0c;用于多方面可控文本生成&#xff0c;它能够通过简单地连接…

AI的突破与融合:2024年中国智能技术的新纪元_光点科技

随着人工智能领域的不断突破&#xff0c;2024年注定将成为中国智能技术发展的一个新纪元。当下&#xff0c;AI技术不仅在理论研究上取得了重大进展&#xff0c;其在商业应用、社会服务等领域的融合也日益深入。本文将结合近期网络上的AI热点&#xff0c;展望中国在AI技术方面的…

信源、信宿与信道

在通信领域中&#xff0c;信源、信宿与信道是不可或缺的三个基本要素&#xff0c;它们相互作用&#xff0c;共同构成了整个通信系统。本文将从信源、信宿和信道的定义、特点以及它们之间的关系进行详细阐述。 一、信源的定义与特点 信源是指产生信息的源头&#xff0c;它可以…

《JVM由浅入深学习【四】 2023-12-24》JVM由简入深学习提升分享

JVM由简入深学习提升分享四 1.JVM中java堆的特点及作用2. JVM中对象如何在堆内存中分配3. JVM堆内存中的对象布局 1.JVM中java堆的特点及作用 是线程共享的一块区域虚拟机启动时就创建了是虚拟机中内存占用很大的一块存放所有的实例对象和数组GC主要的作用区域可分为新生代&am…