在上一篇博文中我们提到:要令排序算法的时间复杂度低于O(n2),必须令算法执行“远距离的元素交换”,使得平均每次交换减少不止1逆序数。

  而希尔排序就是“简单地”将这个道理应用到了插入排序中,将插入排序小小的升级了一下。那么,希尔排序是怎么将这个道理应用于插入排序的呢?我们先来回顾一下插入排序的代码:

void InsertionSort(int *a, unsigned int size)
{//StartPos表示执行插入操作的元素开始插入时的下标//令StartPos从1递增至size-1,对于每个a[StartPos],我们执行向前插入的操作for (int StartPos = 1;StartPos < size;++StartPos) for (int CurPos = StartPos;CurPos != 0;--CurPos)if(a[CurPos - 1] > a[CurPos])swap(&a[CurPos],&a[CurPos-1]);   //令当前元素与前一元素交换
}

  不难看出,在插入排序中,对于每一个元素,我们都令其执行“向前插入”操作,直至到达顺序位置。但是,在“向前插入”这个操作中,每一次“当前元素”都是与前一元素进行比较,而这也是插入排序时间复杂度没能低于O(n2)的原因。

  所以,希尔排序与插入排序之间的区别就是:希尔排序在“向前插入”时,“当前元素”总是与前k元素(若当前元素下标为n,则前k元素即下标为n-k的元素)进行比较,并且第一个开始“插队”的元素不再是[1],而是[k]。从代码角度来说,便是将插入排序的循环改为:

   //StartPos表示执行插入操作的元素开始插入时的下标//令StartPos从k递增至size-1,对于每个a[StartPos],我们执行向前插入的操作for (int StartPos = k;StartPos < size;++StartPos) for (int CurPos = StartPos;CurPos >= k;CurPos-=k)if(a[CurPos - k] > a[CurPos])swap(&a[CurPos],&a[CurPos-k]);   //令当前元素与前k元素交换

  不难看出,插入排序就是k=1的情况。经过上述代码处理后,数据可以保证如下属性:

  a[n],a[n+k],a[n+2k]……a[n+x*k]有序,其中0=<n<size且n+x*k<size,也就是说:所有相隔距离为k的元素组成的数列都有序(当k为1时即全体有序)

  举个实例来看看,假设数组如下,间距为3的元素用同色标注:

  35,30,32,28,12,41,75,15,96,58,81,94,95

  令k=3,进行k=3的“插入排序”后,间距为3的元素互相有序:

  28,12,32,35,15,41,58,30,94,75,81,96,95

  分析上例可以看出,当k>1时,间距为k的k-插入排序的交换可以实现“远距离交换元素”,上例中,3-的插入排序交换了5次元素,逆序数减少了9,平均一次交换减少了1.8逆序数。

  同时可以看出,上述属性,只有在k为1时才能保证整个数组有序,也即普通插入排序的情况,而k>1时则不能。也就是说,要想“远距离交换元素”,就要令k>1,而k>1却又不能保证数组最后有序,那该怎么办呢?

  万幸的是,我们有这么一个定理:

  若数组已经进行过间距为k的k-插入排序,即已经确定间距为k的元素互相有序,则对数组进行间距为(k-1)的(k-1)-插入排序后,数组依然保持“间距为k的元素互相有序”

  用大白话来说,就是:虽然k>1的k-插入排序不能保证数组完全有序,但可以保证不增加数组的逆序数。

  于是,希尔排序的发明者唐纳德·希尔想出了这么一个办法,也就是希尔排序:先进行k比较大的“插入排序”,然后逐步减小k的值,直至k=1。这样一来,希尔排序就能保证最后数组有序。

  接下来的问题就是,k的初始值该如何选?k又该如何减小至1?这一点至关重要,其重要性类似于哈希函数对于哈希表的意义。我们称k从初始值kn减小至1的各值:kn,kn-1,kn-2……1组成的序列称为“增量序列”,即“增量”(Increment,意指k的大小)组成的序列。希尔本人推荐的增量序列是初始值为size/2,任一kn-1=kn/2。这样一来,使用希尔增量序列的希尔排序完整算法如下:

void ShellSort(int *a, unsigned int size)
{unsigned int CurPos, Increment;  //CurPos表示执行插入的元素当下所处的下标,Increment即增量kint temp;   //用于暂存当前执行插入操作的元素值,可以减少交换操作//Increment从size/2开始,按Increment/=Increment的方式递减至1for (Increment = size / 2;Increment > 0;Increment /= 2)//下方代码与插入排序几乎相同,只是比较对象由[CurPos-1]变为[CurPos-Increment]for (unsigned int StartPos = Increment;StartPos < size;++StartPos){temp = a[StartPos];for (CurPos = StartPos;CurPos >= Increment&&a[CurPos - Increment] > temp;CurPos -= Increment)a[CurPos] = a[CurPos - Increment];a[CurPos] = temp;}
}

  接下来,我们以希尔增量序列为例,说明为什么增量序列的设定对于希尔排序性能至关重要:

  设数据为:1,9,2,10,3,11,4,12,则对应增量序列为4,2,1

  4-插入排序后:1,9,2,10,3,11,4,12

  2-插入排序后:1,9,2,10,3,11,4,12

  1-插入排序后:1,2,3,4,9,10,11,12

  不难发现,这个例子中的增量序列很不好,4-排序和2-排序都没有任何的有效操作。这个例子告诉我们两件事:

  1.增量序列对于希尔排序的性能非常重要,差的增量序列会减少需要本可以执行的“远距离交换”

  2.希尔推荐的增量序列编程实现简单,但实际应用中表现并不好,原因在于其增量序列不互素。

  并且可以确定的是,若需排序的数组a大小n为2的幂,任一x为偶数的a[x]均大于x为奇数的a[x],且a[x]>a[x-2],则希尔的增量序列只有在进行1-排序时才有交换操作。

  举例来说:9,1,10,2,11,3,12,4,13,5,14,6,15,7,16,8。

  其增量序列为8,4,2,1,但是8-排序、4-排序与2-排序都没有交换元素。

  此外,若某元素排序前位于下标奇数处,排序后所在位置为i,则进行1-排序前,其位置在2*i+1处(如例中元素4,其下标为奇数,其有序位置应为3,1-排序前位置为7),而将其从位置2*i+1移动至i需要执行i+1次交换,这样的元素(下标奇数)共有n/2个,所以将这些元素移动至正确位置就需要(0+1)+(1+1)+(2+1)+……+(N/2+1)共N2/8-N/4,时间复杂度为O(n2)。可见,使用希尔增量序列希尔排序的最坏情况是O(n2)

  那么,希尔排序的增量序列该如何选择呢?本文给出两个序列,它们都比希尔增量序列要好:

  1.Hibbard序列:{1,3,7……2k-1},k为大于0的自然数,使用Hibbard序列的希尔排序平均运行时间为θ(n5/4),最坏情形为O(n3/2)。

  2.Sedgewick序列:令i为自然数,将9*4-9*2+1的所有结果与4-3*2+1的所有结果进行并集运算,所得数列{1,5,19,41,109……}。使用此序列的希尔排序最坏情形为O(n4/3),平均情形为O(n7/6)

  如何实现这两个序列的希尔排序并不是难事,Hibbard序列可以直接通过计算得出初始值(小于数组大小即可),而后每次令Increment=(Increment-1)/2即可。Sedgewick序列则稍稍麻烦点,需要先将序列计算出足够项(最后一项小于数组大小),而后存于某个数组,再不断从中取出元素作为增量。

  希尔排序的性能(使用Sedgewick序列)在数据量较大时依然是不错的。如果说插入排序是我们的“初级排序”,用于较少数据或趋于有序数据的情况,那么希尔排序就是我们的“中级排序”,用于数据量偏多的情况。当然,当数据量极大时,我们将用上我们的“高级排序”——快速排序。至于怎么样算数据量偏多,这个就需要因情境而异了,数据的存储形式等都是需要考虑的问题,一般来说数据量为万级时我们使用希尔排序,数据量为十万、百万级时使用快速排序,而数据量为百、千级时插入排序和希尔排序都可以考虑。并且需要再次说明的是,数据越趋于有序,则插入排序越快。从这个角度来说,插入排序也不失为一个“高级排序”。

  那么,我们学习堆时提到的用堆进行排序的想法,明明有着很好的时间界O(N*logN),为什么不在考虑之列呢?我们下一篇博文就简单地分析分析。

  

转载于:https://www.cnblogs.com/mm93/p/7544886.html

浅入浅出数据结构(18)——希尔排序相关推荐

  1. 浅入深出之Java集合框架(上)

    Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到浅入深出之Java集合框架(下). ...

  2. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到浅入深出之Java集合框架(下). ...

  3. 「游戏引擎 浅入浅出」项目介绍

    「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book 项目介绍 README 本书 ...

  4. 「游戏引擎 浅入浅出」前言

    「游戏引擎 浅入浅出」是一本开源电子书,Github地址: https://github.com/ThisisGame/cpp-game-engine-book 为什么写这本书? 在与同事沟通时,会提 ...

  5. [科普]浅入浅出Liunx Shellcode

    创建时间:2008-05-13 文章属性:原创 文章提交: pr0cess  (pr0cess_at_cnbct.org) 浅入浅出Liunx Shellcode /*---------------- ...

  6. 浅入浅出深度学习理论实践

    全文共9284个字,40张图,预计阅读时间30分钟. 前言 之前在知乎上看到这么一个问题:在实际业务里,在工作中有什么用得到深度学习的例子么?用到 GPU 了么?,回头看了一下自己写了这么多东西一直围 ...

  7. Java 注解 (Annotation)浅入深出

    Java 注解 (Annotation)浅入深出 本文主要参考与借鉴frank909 文章,但更为简单,详细. Annotation 中文译过来就是注解.标释的意思.Annotation是一种应用于类 ...

  8. 浅入浅出 1.7和1.8的 HashMap

    HashMap 是我们最最最常用的东西了,它就是我们在大学中学习数据结构的时候,学到的哈希表这种数据结构.面试中,HashMap 的问题也是常客,现在卷到必须答出来了,是必须会的知识. 我在学习 Ha ...

  9. 浅入浅出linux中断子系统

    浅入浅出linux中断子系统,如需深入,直接跳转重要参考章节. 什么是中断? 当CPU被某些信号触发,CPU暂停当前工作,转而处理信号的事件,简单的称它为中断,这个信号可以是系统外设的信号,也可能是芯 ...

  10. 浅入浅出Oracle Spatial GeoRaster 10g影像数据管理(2)

    浅入浅出Oracle Spatial GeoRaster  10g 影像数据管理(2)--物理存储 1.物理存储方式概要      在上个部分<浅入浅出Oracle Spatial GeoRas ...

最新文章

  1. Closer--Never Take it for granted
  2. TextVew中文空格
  3. daily scrum 11.1
  4. mysql ereg_php中正则表达式匹配函数ereg是不是被弃用了?
  5. yolo 负样本_目标检测介绍之YOLO与SSD
  6. 领域应用 | 知识计算,华为云赋能企业知识化转型
  7. Django处理ajax请求
  8. Vuex actions 异步操作基础
  9. 二级域名共享cookies
  10. 通过 Powershell 来替换 ARM 模式下虚拟机的网络接口
  11. [附源码]Java计算机毕业设计SSM大众点评管理系统
  12. 计算机专业实践报告立题依据,论文的选题依据.doc
  13. 爬取 百度logo(图片)
  14. 生物计算机是未来型计算机吗,生物计算机特点及未来发展
  15. VOT竞赛paper阅读笔记
  16. 快播将关闭QVOD服务器 清理低俗内容与涉盗版内容
  17. linux系统文件信息系统满,在Deepin系统中提示系统盘已经满了(/home文件大)的解决方案...
  18. Windows 11 已修复 AMD CPU 性能问题
  19. python matplotlib jupyter中绘图使用宋体
  20. 树莓派hc sr501 c语言,树莓派人体红外感应模块HC-SR501安装配置

热门文章

  1. 对List集合中的对象进行按某个属性排序
  2. 服务器资源占用监控(jp@gc - PerfMon Metrics Collector)
  3. windows安装多个jdk版本的切换问题
  4. mysql审计插件安装_MySQL审计插件安装使用说明文档--升级版
  5. linux 判断上一条命令是否指令成功
  6. 把汉化的eclipse还原为英语版的小绝招
  7. 如何转换并压缩png格式图片
  8. [fastjson] - fastjson中 JSONObject 和 JSONArray
  9. sublime text3:呼出安装插件的快捷键
  10. JavaSE_1   基本概念