Python实现经典排序算法--希尔排序

一、希尔排序简介

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

二、性质

1、希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

三、基本思想

先将整个数组从逻辑上进行分组(分治思想),在组内应用快速排序实现局部有序,但是有序程度不是很高,当分组单位为1时,这时实现整体排序仅需要一遍插入排序即可完成。
具体做法是

先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1( < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

四、求解思路

这里利用递归的思想来简化求解,即我们只需要找到该问题的初始解即可。一般以序列的半数作为初始增量,每完成一次局部插入排序,增量倍减,即需要一个外层循环来控制增量的递减过程。
考虑内部,需要一个循环来遍历组内元素,可以用来分别比较组间元素,在考虑到奇数个元素的情况下,后组末尾元素要进行组内插入排序。在比较过程,需要用O(1)的空间来进行存储交换。
下面来看下代码实现过程:

def shellsort(list1):
    if list1!=None:
        if list1==1:
            pass
        else:
            # 初始化将list分为两组,每次间隔倍减
            N = len(list1)
            gap = int(len(list1)/2)
            # 外层循环用于控制gap分组的大小,递减
            while gap > 0:
                for i in range(gap,N):
                    j = i;
                    # 外部存储位,用于数据交换
                    current = list1[i]
                    while(j-gap>=0 and current <list1[j-gap]):
                        list1[j]=list1[j-gap]
                        j = j -gap
                    list1[j] = current
                gap = int(gap / 2)
    return list1
list1=[2,8,7,1,9,3,5,6,4]
shellsort(list1)

希尔排序动图展示:
Alt

五 时空复杂度

5.1 空间复杂度

代码中使用的缓冲空间仅仅是哪个current,即空间复杂度为O(1)。

5.2 时间复杂度

5.2.1 期望值

希尔排序的时间复杂度和增量序列是相关的,即排序的时间复杂度取决于增量序列的排序情况。

  • 对于{1,2,4,8,…}这种序列并不是很好的增量序列,使用这个增量序列的时间复杂度(最坏情形)是 O ( n 2 ) O(n^2) O(n2)
  • Hibbard提出了另一个增量序列{1,3,7,…, 2 k − 1 2^{k-1} 2k1}这种序列的时间复杂度(最坏情形)为 O ( n 1.5 ) O(n^{1.5}) O(n1.5)
  • Sedgewick提出了几种增量序列,其最坏情形运行时间为 O ( n 1.3 ) O(n^{1.3}) On1.3,其中最好的一个序列是{1,5,19,41,109,…}。

有人通过大量的实验,给出了较好的结果:当n较大时,比较和移动的次数约在 n 1.25 n^{1.25} n1.25 1.6 n 1.25 1.6n^{1.25} 1.6n1.25之间,期望大约为 n 1.5 n^{1.5} n1.5

5.2.2 最好情况的特征

好的增量序列的共同特征:

  • 最后一个增量必须为1;
  • 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。

5.2.3 优于插入排序的原因

希尔排序的时间性能优于直接插入排序的原因:

  • 当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  • 当n值较小时,n和 n 2 n^2 n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0( n 2 n^2 n2)差别不大。
  • 在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。

因此,希尔排序在效率上较直接插入排序有较大的改进。

六 稳定性

由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。比如[ 3, 5 ,10, 8 ,7, 2, 8, 1, 20, 6]这个增量序列,在进行分组d=2时,前一组组内排序是[3, 10, 7, 8, 20] ,后一组是[5, 8, 2 , 1, 6],分别经过组内排序分别得到[3,7,8,10,20]和[1, 2, 5, 6, 8],这里第一组的8排序前在第二组后面,经过组内排序,跑到前面去了,这就造成排序的不稳定了。

七、优缺点

7.1 优点

  • 不需要大量的辅助空间,空间复杂度为O(1)。
  • 容易实现,和归并排序一样,代码简单。
  • 希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。
  • 几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法。

7.2 缺点

  • 不够稳定。
  • 时间复杂度并不是最优的。希尔排序的时间的时间复杂度为 O ( n 1.5 ) O( n^{1.5}) O(n1.5),希尔排序时间复杂度的下界是 n l o g n nlogn nlogn。希尔排序没有快速排序算法快$ O(n(logn)) , 因 此 中 等 大 小 规 模 表 现 良 好 , 对 规 模 非 常 大 的 数 据 排 序 不 是 最 优 选 择 。 但 是 比 ,因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比 O(n^2)$复杂度的算法快得多

八 总结

希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。 原因是,当n值很大时数据项每一趟排序需要移动的个数很少,但数据项的距离很长。当n值减小时每一趟需要移动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。Shell算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,今仍然是数学难题。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值