排序算法(03)— 希尔排序

一、概述

希尔排序(Shell Sort) 是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是 直接插入排序 算法的一种更高效的改进版本。希尔排序是 非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。
在这之前的排序算法的时间复杂度基本都是O(n2),希尔排序是算法是突破这个时间复杂度的第一批算法之一。

二、基本思想

我们之前讲过 直接插入排序 ,应该说它的效率在某些情况下是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,这个时候,直接插入排序就很高效。还有就是记录数比较少时,直接插入的优势也很明显。可问题在于,这两个条件都很苛刻,实现起来就比较困难。所以就出现了希尔排序
有的同学就会问,记录数很少我都懂,什么样的数据是基本有序呢?比如 L = {9,1,5,8,3,7,4,6,2},现在将它分为3组{9,1,5},{8,3,7},{4,6,2},各自排好序,{1,5,9},{3,7,8},{2,4,6},然后合并为一个{1,5,9,3,7,8,2,4,6},此时这个序列还是乱序无序的状态,谈不上基本有序,什么才算是基本有序,{2,1,3,6,4,7,5,8,9}就算是基本有序了。
基本有序就是小的关键字基本在前边,大的基本在后边,不大不小的基本在中间。

2.1 希尔排序原理

希尔排序通过分割待排序记录,减少待排序记录的个数,使整个序列向基本有序发展。
跳跃分割策略:将相距某个增量的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

2.2 希尔排序步骤

我们来看下希尔排序的基本步骤:

  1. 下面给出一个数据列:
    在这里插入图片描述

  2. 第一趟取increment的方法是:n/3 向下取整 +1 = 3(关于increment的取法之后会有介绍)。将整个数据列划分为间隔为3的3个子序列,然后对每一个子序列执行直接插入排序,相当于对整个序列执行了部分排序调整。图解如下:
    在这里插入图片描述

  3. 第二趟将间隔 increment = increment / 3 向下取整 +1 = 2,将整个元素序列划分为2个间隔为2的子序列,分别进行排序。图解如下:
    在这里插入图片描述

  4. 第3趟把间隔缩小为 increment = increment / 3向下取整 +1 = 1,当增量为1的时候,实际上就是把整个数列作为一个子序列进行插入排序,图解如下:
    在这里插入图片描述

2.3 核心实现

/* 希尔排序算法*/

void ShellSort(SqList * L)
{
    int i,j;
    int increment = L->length;
    do
    {
        increment = increment / 3 + 1;
        for (i = increment+1; i < L->length; i ++)
        {
            if (L->r[i] < L->r[i-increment])
            {
                L->r[0] = L->r[i];                  // 暂存
                for (j = i-increment; j>0 && L->r[0] < L->r[j]; j -= increment)
                    L->r[j+increment] = L->r[j];    // 记录后移,查找插入位置
                L->r[j+increment] = L->r[0];        // 插入
            }
        }
    }
    while (increment > 1); // increment == 1 时跳出循环
}

三、希尔排序复杂度分析

看到这里相信大家也应该明白了,希尔排序的关键并不是随便分组后自排序,而是将相隔某个增量的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

3.1 关于希尔排序增量的取法

增量increment的取法有各种方案。最初Shell提出取 increment = n / 2向下取整,increment = increment / 2向下取整,直到increment = 1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来Knuth提出取 increment = n / 3向下取整 +1.还有人提出都取奇数为好,也有人提出increment互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。 可究竟选取什么样的增量才是最好,目前还是一个数学难题,迄今为止还没有找到一种最好的增量序列。不过大量研究表明,当增量序列为
d l t a [ k ] = 2 t − k + 1 − 1 ( 0 ≤ k ≤ t ≤ ⌊ l o g 2 ( n + 1 ) ⌋ ) dlta[k] = 2^{t-k+1}-1 (0\leq k\leq t\leq ⌊log_2(n+1) ⌋) dlta[k]=2tk+11(0ktlog2(n+1))
时,可以获得不错的效率,其时间复杂度为O(n3/2),要好于直接插入排序的O(n2)。需要注意的是,增量序列的最后一个增量值必须等于1才行。

四、总结

希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。

五、相关连接:

基础算法(01)— 三种简单排序(冒泡、插入、选择)
基础算法(02)— 快速排序算法

参考文档

[1]严蔚敏、吴伟民. 数据结构(C语言版). 北京:清华大学出版社,1997
[2]程杰. 大话数据结构. 北京:清华大学出版社,2011

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值