希尔排序(一)

希尔排序的名称源于它的发明者——唐纳德·希尔(Donald Shell)。

希尔排序是另一种形式的插入排序,它神奇地突破了冒泡排序、直接选择排序、直接插入排序等算法的二次时间界限,在时间复杂度上首次实现了质的突破。

希尔排序如此神奇,这源于它对插入排序两个优点的综合应用:

  1. 在数据量少的时候插入排序速度很快
  2. 在数据几乎有序的时候插入排序速度很快

什么是希尔排序

我们通过一个例子解释希尔排序使用的策略。假设我们要对随机数组 A[16] 进行排序,其中的元素用 a0 ~ a15 表示。

一、 从 a0 开始,按照 1,2,3,…,8 报数,报相同数字的分为一组。即将 16 个数据分为 8 组:

[a0, a8],

[a1, a9],

[a2, a10],

[a3, a11],

[a4, a12],

[a5, a13],

[a6, a14],

[a7, a15]。

注意:这里说的是逻辑上的分组,不是真的把数据交换位置。在代码实现上我们只要用跳跃的数组下标就可以。

这 8 组数据每一组都只包含两个元素,通过插入排序可以快速完成(插入排序的第一个优点)。排序完成后的数组用 B[16] 表示;

二、从 b0 开始,按 1,2,3,4 报数。报相同数字的分为一组,这样就将 B[16] 分为 4 组:

[b0, b4, b8, b12],

[b1, b5, b9, b13],

[b2, b6, b10, b14],

[b3, b7, b11, b15]。

这 4 组数据每一组中的元素个数比第一步要多一些,但是每一组都有一个特点:比较有序。比如第一组: b0b8 是有序的,b4b12 也是有序的。这样我们就可以利用插入排序第二个优点,对每一组进行插入排序。第二步完成后的数组用 C[16] 表示;

三、从 c0 开始,按 1,2 报数。报相同数字的分为一组,这样就将 C[16] 分为 2 组:

[c0, c2, c4, c6, c8, c10, c12, c14],

[c1, c3, c5, c7, c9, c11, c13, c15]。

这 2 组数据每一组中的元素个数比上一步又要多一些,但是它的有序程度也更明显。比如第一组中的c0, c4, c8, c12 是有序的,c2, c6, c10, c14 也是有序的。这样我们还是可以利用插入排序的第二个优点,对每一组进行插入排序;

四、对所有元素进行排序。虽然元素数量是这四步中最多的,但是这时候元素的有序程度也是最高的

这样我们就通过 4 组插入排序完成了对 16 个元素的排序工作。请注意,这 4 组中的每一组都有这样一个特点:要么数据量很小(第一组),要么数据量越来越大但是有序程度越来越高(后三组)。

可以看到,希尔排序在数据量和数据有序程度上进行了折中安排。虽然进行了多次插入排序,但是由于插入排序是二次的而不是线性的,所以小规模的多次插入排序快于大规模的一次插入排序。

值得注意的是,希尔排序必须在最后一组进行完整的插入排序,否则结果一般不会正确。

总结上面的方法,我们得到希尔排序的一般策略:

希尔排序使用一个序列 h1 h 1 h2 h 2 h3 h 3 ... . . . ht h t 叫做增量序列。只要 h1=1 h 1 = 1 ,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好(后文会说到)。在使用增量 hk h k 的一趟排序后,对于每一个 i i ,我们有 a[i]a[i+hk],此时称文件是 hk h k - 排序 的。

hk h k - 排序 的一般做法是:

a1 a 1 a1+hk a 1 + h k a1+2hk a 1 + 2 h k ... . . . ,分为一组;
a2 a 2 a2+hk a 2 + h k a2+2hk a 2 + 2 h k ... . . . ,分为一组;
a3 a 3 a3+hk a 3 + h k a3+2hk a 3 + 2 h k ... . . . ,分为一组;
... . . .
... . . .

ahk a h k a2hk a 2 h k a3hk a 3 h k ... . . . , 分为一组。

一趟 hk h k - 排序 的作用就是对 hk h k 个独立的子数组执行一次插入排序。

在增量序列的选择上,比较流行的做法是使用Shell建议的序列:

ht=floor(N/2) h t = f l o o r ( N / 2 )
hk=floor(hk+1/2) h k = f l o o r ( h k + 1 / 2 )

也就是我们上面介绍的方法。

C语言实现

数组打印函数

void print_array(int a[],  int len, int gap)
{
    for(int i=0; i<len; ++i)
    {
        printf("[%d]:%2d  ", i, a[i]);
        if((i + 1) % gap == 0)
            printf("\n");
    }
    printf("\n\n");
}

这个函数是专门为希尔排序设计的。我想把排序的过程打印出来,那自然少不了分组。gap这个参数用来传递 hk h k - 排序 hk h k

因为有if((i + 1) % gap == 0)这个条件判断,所以每打印 hk h k 个数就换行一次,所以看的时候要竖着看,每一纵列就是一组,一共有 hk h k 组。

如果不希望换行打印,则可以给gap传一个比数组长度大的参数。

排序函数

这个是原原本本地按照希尔排序的步骤而写的。

如果在编译的时候定义了宏PRINT_PROCEDURE,则可以打印出排序的具体过程,对理解希尔排序非常有帮助。

代码就不多说了,因为有详细的注释。

void shellsort(int arr[], int n)  
{  
    //步长采用shell序列
    for (int gap = n / 2; gap > 0; gap /= 2) 
    {  
#ifdef PRINT_PROCEDURE
        printf("-------- gap = %d--------\n",gap);
        print_array(arr, n, gap);
#endif       
        for (int i = 0; i < gap; i++)       
        {
#ifdef PRINT_PROCEDURE          
            printf("column %d :\n",i);  // 对列i排序
#endif
            for (int j = i + gap; j < n; j += gap) 
            {   // 每次加上步长,即按列排序。 
                // if 条件成立说明arr[j]需要插入到某个位置
                if (arr[j - gap] > arr[j]) 
                {  
                    // 因为arr[j]会被前面的记录覆盖,所以先暂存
                    int temp = arr[j]; 
                    int k = j - gap;  // k指向arr[j-gap],从后往前遍历
                    while (k >= 0 && arr[k] > temp) 
                    {  
                        arr[k + gap] = arr[k];  // arr[k]向后移动 
                        k -= gap;  
                    }  
                    // 把arr[j]插入到arr[k]的后面
                    arr[k + gap] = temp; 
#ifdef PRINT_PROCEDURE
                    printf("[%d] insert to [%d]\n", j, k+gap);
#endif                              
                } 
            }
        }
#ifdef PRINT_PROCEDURE
        printf("\n");
        print_array(arr, n, gap);
#endif      
    }  
}  

赶紧写个测试函数,看看排序的过程吧。

测试函数

#define DUMMY_GAP 100

int main(void)
{
    int array[] = {5,2,8,9,3,9,7,1,0,4,}; // 10个数
    print_array(array,sizeof(array)/sizeof(array[0]),DUMMY_GAP);

    shellsort(array, sizeof(array)/sizeof(array[0]));

    print_array(array,sizeof(array)/sizeof(array[0]),DUMMY_GAP);
    return 0;
}

运行结果

这里写图片描述
从上图可以看出,一共分成了5组(竖着看),对每一组都进行直接插入排序。

这里写图片描述

这里写图片描述
上图是 hk=1 h k = 1 的情况,对所有元素进行排序。虽然元素数量是这3次中最多的,但是这时候元素的有序程度也是最高的。

【未完待续】

参考资料

《数据结构与算法分析(原书第2版)》(机械工业出版社,2004)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值