C语言希尔排序及其增量序列
希尔排序
前情提要: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}
- 如果数组元素大于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,其变化过程为
- abcdefgh
- bacdefgh(b和a比较一次)
- bdacefgh(c和a比较一次,d和b,a,c分别比较一次)
- bdfacegh(e和c比较一次,f和d,a,c,e分别比较一次)
- bdfhaceg(g和e比较一次,h和f,h,a,c,e分别比较一次)
共计16次比较。
对于长度为2N2N2N的序列{ai}=a1a2...a2N\{a_i\}=a_1a_2...a_{2N}{ai}=a1a2...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}a1a2...a2N变化为a2a4...a2Na1...a2N−1a_2a_4...a_{2N}a_1...a_{2N-1}a2a4...a2Na1...a2N−1的过程相当于是a2a4...a2Na_2a_4...a_{2N}a2a4...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=Nj+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语言希尔排序及其增量序列相关推荐
- 【数据结构笔记33】C实现:希尔排序、增量序列
本次笔记内容: 9.2 希尔排序 文章目录 希尔排序 希尔排序方法 希尔排序实现 改进:更多增量序列 希尔排序 希尔排序方法 由Donald Shell提出,应用了插入排序的简单,但是又克服了插入排序 ...
- C语言希尔排序(解析)
C语言希尔排序(解析) 网上找的移动图:
- 图解,C语言希尔排序
希尔排序和插入排序很相似,有点像插入排序的升级版本. 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法.希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本 ...
- 数据结构之插入排序:希尔排序(缩小增量排序)
排序算法:希尔排序.缩小增量排序 思维导图: 希尔排序的定义: 例: 希尔排序d的选取: 希尔排序的代码实现: 希尔排序的性能: 思维导图: 希尔排序的定义: 普通的插入算法正序时时间复杂度会很小,但 ...
- C语言 希尔排序 使用监视哨
文章目录 算法介绍 思想讲解 优点 代码 运行结果 算法介绍 希尔排序(Shell's Sort)是插入排序的一种又称"缩小增量排序"(Diminishing Increment ...
- matlab实现希尔排序,C语言希尔排序算法
用希尔排序法对一组数据由小到大进行排序,数据分别为 69.56.12.136.3.55.46. 99.88.25. 实现过程: (1)自定义函数 shsort(),实现希尔排序. (2) main() ...
- 【教程】C语言希尔排序算法
用希尔排序法对一组数据由小到大进行排序,数据分别为 69.56.12.136.3.55.46. 99.88.25. 例子: (1)自定义函数 shsort(),实现希尔排序. (2) main() 函 ...
- 希尔排序c语言,希尔排序(C/C++实现)
封装成函数: //交换数组元素 void swap(int *a,int i,int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } //希尔排序 void s ...
- 高效排序算法——希尔排序、堆排序、归并排序、快速排序
如标题,这里讨论的是基于比较的排序算法中最高效的三种算法和希尔排序.堆排序.归并排序.快速排序的平均时间复杂度均为O(NlogN).前面有介绍过O(N2)的三种简单排序算法(见三大简单排序算法--插入 ...
最新文章
- 第三代测序之Pacific Biosciences
- 干货!图神经网络及其自监督学习
- 数组、ArrayList、链表、LinkedList
- 声明式事务控制的实现
- 现代交换技术学习笔记001
- try catch线程问题???
- python基础面试题1
- 【IDEA】Error:java: Compilation failed: internal java compiler error
- php多个逻辑如何分为多个逻辑块,php 项目如何分层
- python msi installer_Windows10 MYSQL Installer 安装(mysql-installer-community-5.7.19.0.msi)
- 自然语言处理实战:小说读取及分析(附代码)
- 关于安卓(apk)unity3d游戏汉化简单做一些全面分析
- 小米mix2安兔兔html5跑分,vivo X21跑分多少?高通骁龙660 AIE安兔兔跑分实测
- ios开发 多人语音聊天_iOS语音提醒开发总结
- 计算机基础知识大全之硬件篇
- Pandas数据分析——从0.3到0.8学习指南
- 【taro react】---- 兼容微信小程序和H5的海报绘制插件
- 指南-Luat二次开发教程指南-功能开发教程-socket
- 如何恢复已删除的照片
- VUE项目(仿商城)
热门文章
- 苹果电脑双系统如何切换,CrossOver兼容双系统无需切换轻松帮你解决
- 完全平方数(c++基础)
- [leetcode javascript]周赛155:5197. 最小绝对差(没有做完
- php访问违例,关于UG内存访问违例的简单而有效的解决办法!!
- python 导入模型_scikit-learn系列之如何存储和导入机器学习模型
- 【三维目标检测】Second 模型 (一)
- STM32入门开发--LED模块实现跑马灯
- 英语语法总结--状语从句
- java实现随机数抽奖_JAVA使用随机数实现概率抽奖
- 485、CAN、单总线、SPI、I2C的概念,特点,协议,使用方法及通信方式,还有它们之间的区别