希尔排序

前情提要:C语言排序算法

插入排序的神奇之处在于,只要运气够好,甚至可以达到O(n)O(n)O(n)的时间复杂度,希尔排序的思想就是想办法放大这种运气。

希尔排序又称缩小增量排序,据说是第一个突破O(n2)O(n^2)O(n2)的排序算法,选择不同的增量,拥有不同的时间复杂度。

算法步骤\textbf{算法步骤}算法步骤

设数组有nnn个元素,{a0,a1,…,an}\{a_0,a_1,\ldots,a_n\}{a0​,a1​,…,an​}

  1. 如果数组元素大于2,则将数组分成左数组和右数组,并对左数组和右数组的元素进行一对一地排序。
  2. 对每一个数组进行细分,然后将每个子数组进行一对一地排序。

例如下面的数组

1 8 2 9 3 0 11 13 12 5 7 15 14 4 6 3

首先分为左右两个数组,对这两个数组按位排序

排序前 左 1 8 2 9 3 0 11 13
排序前 右 12 5 7 15 14 4 6 3
排序后 左 1 5 2 9 3 0 6 3
排序后 右 12 8 7 15 14 4 11 13

然后将左右两个数组再次分为左右两个数组,

排序前 左左 1 5 2 9
排序前 左右 3 0 6 3
排序前 右左 12 8 7 15
排序前 右右 14 4 11 13
排序。。。
排序后 左左 1 0 2 3
排序后 左右 3 4 6 9
排序后 右左 12 5 7 13
排序后 右右 14 8 11 15

那么现在总共有4列,左左、左右、右左、右右四列,为了书写方便,将左记为0,右记为1,这四列再分为8列

000 001 010 011 100 101 110 111 000 001 010 011 100 101 110 111
1 2 3 6 12 7 14 11 1 2 3 6 7 11 12 14
0 3 5 9 4 13 8 15 0 3 4 5 8 9 13 15

这时的序列为

1 0 2 3 3 4 6 5 7 8 11 9 12 13 14 15

还需要最后一步,即对这个序列进行排序。

问题的关键就是每次的组间排序用什么算法,如果用选择排序,那么希尔排序非但毫无意义,甚至会增加更多的时间。

如果使用插入排序,那么这两种算法在时间上的下限都是O(n)O(n)O(n),对于具有16个元素的数组而言,共需排序4次,每次需要

排序次数 每次排序的数据量 每次排序的计算量
1 8组,每组2位乱序 8次比较
2 4组,每组4位,部分有序 <5×4次比较
3 2组,每组8位,部分有序 <16×2次比较
4 1组,16位,部分有序 <50次比较

对于第2次排序,假设每一组的4位数字是abcd,所谓部分有序,就是存在

a<c,b<da<c, b<d a<c,b<d

而最差的情况无非是

b<d<a<cb<d<a<c b<d<a<c

采用插入排序,其排序过程为abcd→bacd→bdac,其中,b和a需要比较一次,c和a比较一次,d和b,a,c分别比较一次,共需5次比较。

对于第三次排序,假设每一组的8位数字是abcdefgh,且存在

a<c<e<g,b<d<f<ha<c<e<g,\quad b<d<f<h a<c<e<g,b<d<f<h

则其最差的结果无非是

b<d<f<h<a<c<e<gb<d<f<h<a<c<e<g b<d<f<h<a<c<e<g

要求abcdefgh→bdfhaceg,其变化过程为

  1. abcdefgh
  2. bacdefgh(b和a比较一次)
  3. bdacefgh(c和a比较一次,d和b,a,c分别比较一次)
  4. bdfacegh(e和c比较一次,f和d,a,c,e分别比较一次)
  5. bdfhaceg(g和e比较一次,h和f,h,a,c,e分别比较一次)

共计16次比较。

对于长度为2N2N2N的序列{ai}=a1a2...a2N\{a_i\}=a_1a_2...a_{2N}{ai​}=a1​a2​...a2N​,若其经过一次增量排序,则必符合

ai<ai+2a_i<a_{i+2} ai​<ai+2​

其最差情况为

a2<a4<...<a2N<a1<a3<...<a2N−1a_2<a_4<...<a_{2N}<a_1<a_3<...<a_{2N-1} a2​<a4​<...<a2N​<a1​<a3​<...<a2N−1​

则由a1a2...a2Na_1a_2...a_{2N}a1​a2​...a2N​变化为a2a4...a2Na1...a2N−1a_2a_4...a_{2N}a_1...a_{2N-1}a2​a4​...a2N​a1​...a2N−1​的过程相当于是a2a4...a2Na_2a_4...a_{2N}a2​a4​...a2N​逐一插入到a1...a2N−1a_1...a_{2N-1}a1​...a2N−1​中,当j>1j>1j>1时,a2ja_{2j}a2j​的前面共有jjj个a2j−1a_{2j-1}a2j−1​,再算上已经排序完成的a2j−2a_{2j-2}a2j−2​,则需要比较j+1j+1j+1次;而a2j−1a_{2j-1}a2j−1​则只需和a2j−3a_{2j-3}a2j−3​比较一次。

所以对于长度为2N2N2N的序列,共需比较

N+∑j=2j=Nj+1=N+(N−1)(N+4)2N+\sum_{j=2}^{j=N}j+1=N+\frac{(N-1)(N+4)}{2} N+j=2∑j=N​j+1=N+2(N−1)(N+4)​

则对于长度为2N2^N2N的序列,希尔排序共需比较

∑i=1N(2i+(2i−1)(2i+4)2)×2N−i=2N∑i=1N(1+(1−2−i)(2i+4)2)=2N∑i=1N(52+2i−1+21−i)≈2N(2N−1)\begin{aligned} &\sum_{i=1}^N(2^i+\frac{(2^i-1)(2^i+4)}{2})\times2^{N-i}\\ =&2^N\sum_{i=1}^N(1+\frac{(1-2^{-i})(2^i+4)}{2})\\ =&2^N\sum_{i=1}^N(\frac{5}{2}+2^{i-1}+2^{1-i})\\ \approx&2^N(2^N-1) \end{aligned} ==≈​i=1∑N​(2i+2(2i−1)(2i+4)​)×2N−i2Ni=1∑N​(1+2(1−2−i)(2i+4)​)2Ni=1∑N​(25​+2i−1+21−i)2N(2N−1)​

可见,希尔排序在最差的情况下也需要O(n2)O(n^2)O(n2)的时间复杂度,排序算法可用C语言写成下面的样子:

//希尔排序
void ShellSort(double *arr, int n){int i,j,nSub;for (nSub = n/2; nSub >0; nSub/=2)   //nSub为子数组长度for (i = nSub; i < n; i++){tmp = arr[i];for (j = i-nSub; j >= 0 && tmp>arr[j]; j -= nSub)arr[j+nSub] = arr[j];arr[j+nSub] = tmp;}
}
int main(){testSort(6,InsertionSort,'>');return 0;
}

结果为

>gcc sort.c
>a
the original list: 927.50 1565.60 750.80 2276.00 1512.30 673.60
the sorted list: 2276.00>1565.60>1512.30>927.50>750.80>673.60

然而前面也提到了,希尔排序是第一个突破O(n2)O(n^2)O(n2)的算法,怎么在这里失灵了呢?

主要是增量序列选的不对,刚刚选取的增量序列其实是

1,2,4,...,2n1,2,4,...,2^n 1,2,4,...,2n

很不幸是表现最差的增量序列之一,前人总结了一些高效的增量序列,

递推公式 最差 平均
Shell h0=⌊N2⌋,hn=⌊hn−12⌋h_0=\lfloor\frac{N}{2}\rfloor,h_n=\lfloor\frac{h_{n-1}}{2}\rfloorh0​=⌊2N​⌋,hn​=⌊2hn−1​​⌋ O(n2)O(n^{2})O(n2) O(n1.3)O(n^{1.3})O(n1.3)
Hibbard hn=2n−1h_n=2^n-1hn​=2n−1 O(n1.5)O(n^{1.5})O(n1.5) O(n1.25)O(n^{1.25})O(n1.25)
Sedgewick hn=max⁡(9×4n−9×2n+1,4n−3×2n+1)h_n=\max(9\times4^n-9\times2^n+1,\\4^n-3\times2^n+1)hn​=max(9×4n−9×2n+1,4n−3×2n+1) O(n43)O(n^\frac{4}{3})O(n34​) O(n76)O(n^\frac{7}{6})O(n67​)
Knuth hn=3n−12h_n=\frac{3^n-1}{2}hn​=23n−1​
Gonnet h0=⌊N2.2⌋,hn=⌊hn−12.2⌋h_0=\lfloor\frac{N}{2.2}\rfloor,h_n=\lfloor\frac{h_{n-1}}{2.2}\rfloorh0​=⌊2.2N​⌋,hn​=⌊2.2hn−1​​⌋

下面对Hibbard进行C语言实现,其中,步长为step的插入排序其实很简单,可直接修改插入排序

void ShellInsert(double *arr, int n, int s, int step){int j;for (int i = 1; i < (n-s)/step; i++){j = i;tmp  = arr[s+(j--)*step];while (tmp>arr[s+j*step] && j>0)arr[s+j*step] = arr[s+(--j)*step];  //第j个元素后移arr[s+j*step] = tmp;}
}

当然这种写法过于暴力,故稍作改动,然后进行排序

void shellInsert(double* arr, int n, int s, int step)
{int j;for (int i = s + step; i < n; i += step){tmp = arr[i];j = i-step;while(tmp>arr[j] && j>s){arr[j] = arr[j-step];j -= step;}arr[j] = tmp;}
}void HibbardSort(double* arr, int n)
{for(int step = (n+1)/2-1, step > 0 , step = (step+1)/2-1)for (int i = 0; i < step; i++)ShellInsert(arr, n, i, step);
}

C语言希尔排序及其增量序列相关推荐

  1. 【数据结构笔记33】C实现:希尔排序、增量序列

    本次笔记内容: 9.2 希尔排序 文章目录 希尔排序 希尔排序方法 希尔排序实现 改进:更多增量序列 希尔排序 希尔排序方法 由Donald Shell提出,应用了插入排序的简单,但是又克服了插入排序 ...

  2. C语言希尔排序(解析)

    C语言希尔排序(解析) 网上找的移动图:

  3. 图解,C语言希尔排序

    希尔排序和插入排序很相似,有点像插入排序的升级版本. 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法.希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本 ...

  4. 数据结构之插入排序:希尔排序(缩小增量排序)

    排序算法:希尔排序.缩小增量排序 思维导图: 希尔排序的定义: 例: 希尔排序d的选取: 希尔排序的代码实现: 希尔排序的性能: 思维导图: 希尔排序的定义: 普通的插入算法正序时时间复杂度会很小,但 ...

  5. C语言 希尔排序 使用监视哨

    文章目录 算法介绍 思想讲解 优点 代码 运行结果 算法介绍 希尔排序(Shell's Sort)是插入排序的一种又称"缩小增量排序"(Diminishing Increment ...

  6. matlab实现希尔排序,C语言希尔排序算法

    用希尔排序法对一组数据由小到大进行排序,数据分别为 69.56.12.136.3.55.46. 99.88.25. 实现过程: (1)自定义函数 shsort(),实现希尔排序. (2) main() ...

  7. 【教程】C语言希尔排序算法

    用希尔排序法对一组数据由小到大进行排序,数据分别为 69.56.12.136.3.55.46. 99.88.25. 例子: (1)自定义函数 shsort(),实现希尔排序. (2) main() 函 ...

  8. 希尔排序c语言,希尔排序(C/C++实现)

    封装成函数: //交换数组元素 void swap(int *a,int i,int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } //希尔排序 void s ...

  9. 高效排序算法——希尔排序、堆排序、归并排序、快速排序

    如标题,这里讨论的是基于比较的排序算法中最高效的三种算法和希尔排序.堆排序.归并排序.快速排序的平均时间复杂度均为O(NlogN).前面有介绍过O(N2)的三种简单排序算法(见三大简单排序算法--插入 ...

最新文章

  1. 第三代测序之Pacific Biosciences
  2. 干货!图神经网络及其自监督学习
  3. 数组、ArrayList、链表、LinkedList
  4. 声明式事务控制的实现
  5. 现代交换技术学习笔记001
  6. try catch线程问题???
  7. python基础面试题1
  8. 【IDEA】Error:java: Compilation failed: internal java compiler error
  9. php多个逻辑如何分为多个逻辑块,php 项目如何分层
  10. python msi installer_Windows10 MYSQL Installer 安装(mysql-installer-community-5.7.19.0.msi)
  11. 自然语言处理实战:小说读取及分析(附代码)
  12. 关于安卓(apk)unity3d游戏汉化简单做一些全面分析
  13. 小米mix2安兔兔html5跑分,vivo X21跑分多少?高通骁龙660 AIE安兔兔跑分实测
  14. ios开发 多人语音聊天_iOS语音提醒开发总结
  15. 计算机基础知识大全之硬件篇
  16. Pandas数据分析——从0.3到0.8学习指南
  17. 【taro react】---- 兼容微信小程序和H5的海报绘制插件
  18. 指南-Luat二次开发教程指南-功能开发教程-socket
  19. 如何恢复已删除的照片
  20. VUE项目(仿商城)

热门文章

  1. 苹果电脑双系统如何切换,CrossOver兼容双系统无需切换轻松帮你解决
  2. 完全平方数(c++基础)
  3. [leetcode javascript]周赛155:5197. 最小绝对差(没有做完
  4. php访问违例,关于UG内存访问违例的简单而有效的解决办法!!
  5. python 导入模型_scikit-learn系列之如何存储和导入机器学习模型
  6. 【三维目标检测】Second 模型 (一)
  7. STM32入门开发--LED模块实现跑马灯
  8. 英语语法总结--状语从句
  9. java实现随机数抽奖_JAVA使用随机数实现概率抽奖
  10. 485、CAN、单总线、SPI、I2C的概念,特点,协议,使用方法及通信方式,还有它们之间的区别