漫画:什么是希尔排序?

640?wx_fmt=gif

作者 | 程序员小灰

责编 | 伍杏玲

640?wx_fmt=jpeg

640?wx_fmt=jpeg

—————  第二天  —————

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

————————————

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

让我们先来回顾一下插入排序

插入排序顾名思义,就是在排序的过程中,把数组的每一个元素按照大小关系,插入到前面有序区的对应位置。

比如下面数组中的元素3,按照大小关系,需要插入到前面有序区三个元素之前,而前面三个元素则相应向后挪动:

640?wx_fmt=png

640?wx_fmt=png

 

640?wx_fmt=png

640?wx_fmt=png

以此类推,插入排序一共会进行(数组长度-1)轮,每一轮的结果如下:

640?wx_fmt=png

插入排序的平均时间复杂度是O(n^2)。这个排序算法并不复杂,但显然并不是一个高效的排序算法。

那么,怎样可以对插入排序算法做出优化呢?我们不妨从插入排序的两个特点入手:

1.在大多数元素已经有序的情况下,插入排序的工作量较小

这个结论很明显,如果一个数组大部分元素都有序,那么数组中的元素自然不需要频繁地进行比较和交换。

2.在元素数量较少的情况下,插入排序的工作量较小

这个结论更加显而易见,插入排序的工作量和n的平方成正比,如果n比较小,那么排序的工作量自然要小得多。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

如何对原始数组进行预处理呢?聪明的科学家想到了一种分组排序的方法,以此对数组进行一定的“粗略调整”。

640?wx_fmt=png

所谓分组,就是让元素两两一组,同组两个元素之间的跨度,都是数组总长度的一半,也就是跨度为4。

640?wx_fmt=png

如图所示,元素5和元素9一组,元素8和元素2一组,元素6和元素1一组,元素3和元素7一组,一共4组。

接下来,我们让每组元素进行独立排序,排序方式用直接插入排序即可。由于每一组的元素数量很少,只有两个,所以插入排序的工作量很少。每组排序完成后的数组如下:

640?wx_fmt=png

这样一来,仅仅经过几次简单的交换,数组整体的有序程度得到了显著提高,使得后续再进行直接插入排序的工作量大大减少。这种做法,可以理解为对原始数组的“粗略调整”。

但是这样还不算完,我们可以进一步缩小分组跨度,重复上述工作。把跨度缩小为原先的一半,也就是跨度为2,重新对元素进行分组:

640?wx_fmt=png

如图所示,元素5,1,9,6一组,元素2,3,8,7一组,一共两组。

接下来,我们继续让每组元素进行独立排序,排序方式用直接插入排序即可。每组排序完成后的数组如下:

640?wx_fmt=png

此时,数组的有序程度进一步提高,为后续将要进行的排序铺平了道路。

最后,我们把分组跨度进一步减小,让跨度为1,也就等同于做直接插入排序。经过之前的一系列粗略调整,直接插入排序的工作量减少了很多,排序结果如下:

640?wx_fmt=png

让我们重新梳理一下分组排序的整个过程:

640?wx_fmt=png

像这样逐步分组进行粗调,再进行直接插入排序的思想,就是希尔排序,根据该算法的发明者,计算机科学家Donald Shell的名字所命名。

上面示例中所使用的分组跨度(4,2,1),被称为希尔排序的增量,增量的选择可以有很多种,我们在示例中所用的逐步折半的增量方法,是Donald Shell在发明希尔排序时提出的一种朴素方法,被称为希尔增量。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

 

public static void sort(int [] array){


//希尔排序的增量


int d=array.length;


while(d>1) {


//使用希尔增量的方式,即每次折半

        d=d/2;


for(int x=0;x<d;x++) {


for(int i=x+d;i<array.length;i=i+d) {


int temp=array[i];


int j;


for(j=i-d;j>=0&&array[j]>temp;j=j-d) {

                    array[j+d]=array[j];

                }

                array[j+d]=temp;

            }

        }

    }

}



public static void main(String [] args)

{


int[] array = {5,3,9,12,6,1,7,2,4,11,8,10};

    sort(array);


System.out.println(Arrays.toString(array));

}

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=png

看看上面这个数组,如果我们照搬之前的分组思路,无论是以4为增量,还是以2为增量,每组内部的元素都没有任何交换。一直到我们把增量缩减为1,数组才会按照直接插入排序的方式进行调整。

对于这样的数组,希尔排序不但没有减少直接插入排序的工作量,反而白白增加了分组操作的成本。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

如何为希尔排序选择更有效的增量方式呢?

为了保证分组粗调没有盲区,每一轮的增量需要彼此“互质”,也就是没有除1之外的公约数。

于是,人们相继提出了很多种增量方式,其中最具代表性的是Hibbard增量和Sedgewick增量。

Hibbard的增量序列如下:

1,3,7,15......

通项公式 2^k-1

利用此种增量方式的希尔排序,最坏时间复杂度是O(n^(3/2))

Sedgewick的增量序列如下:

1, 5, 19, 41, 109......

通项公式 9*4^k - 9*2^k + 1 或者 4^k - 3*2^k + 1

利用此种增量方式的希尔排序,最坏时间复杂度是O(n^(4/3))

关于这两种增量方式的时间复杂度,有些需要很复杂的数学证明,有些是人们的大致猜想,大家暂时不用纠结。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=png

上面数组当中,有两个元素5,绿色的5在前,橙色的5在后。

假如我们按照希尔增量分组,第一轮粗调(增量为4)之后,绿色的元素5会跟元素4交换,换到橙色的5后面:

640?wx_fmt=png

第二轮粗调(增量为2)之后:

640?wx_fmt=png

最终排序结果:

640?wx_fmt=png

相同的元素5,在排序之后改变了次序,由此可见希尔排序是一个不稳定排序。

640?wx_fmt=jpeg

【END】

Python这么热,北京这么热,来看看北京的Python有多热?

https://edu.csdn.net/topic/python115?utm_source=cxrs_bw

随着智能物联迅速的兴起,场景联动越来越普遍,作为敲门砖的连接服务该如何实现?

360 资深工程师深度揭秘 360 IoT 云平台连接服务的技术框架实现细节、物联网协议应用和多协议,多网络的落地实践以及连接服务未来的演进方向。

技术干货来袭!立即扫码报名!

640?wx_fmt=jpeg

 热 文 推 荐 

 

☞马云淡出「理想国」

规模比互联网大 30 倍的物联网,入门太难了!

60 多万 GPS 定位服务被曝漏洞,用户信息或将暴露!

顶配 12699 元、没有 5G,“浴霸三摄”的 iPhone 到底长什么样?

☞备受期待的原子交换,将如何对中心化交易所构成威胁?

上云难?TA霸气回应:不要你觉得,一键上云 so easy!

5大必知的图算法,附Python代码实现

适合小团队作战,奖金+招聘绿色通道,这一届算法大赛关注下?

首批共享单车死于 2019

640?wx_fmt=png

你点的每个“在看”,我都认真当成了喜欢

 

 

  • 199
    点赞
  • 345
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值