ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

数据结构与算法——希尔排序

2020-12-19 21:02:14  阅读:196  来源: 互联网

标签:arr 排序 step 希尔 Shell 增量 序列 数据结构


原文链接:https://jiang-hao.com/articles/2020/algorithms-algorithms-shell-sort.html

目录

算法原理

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

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

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

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。该方法实质上是一种分组插入方法

希尔排序把元素按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。

我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。

img

代码实现

在希尔排序的理解时,我们倾向于对于每一个分组,逐组进行处理,但在代码实现中,我们可以不用这么按部就班地处理完一组再调转回来处理下一组(这样还得加个for循环去处理分组)比如[5,4,3,2,1,0] ,首次增量设step=length/2=3,则为3组[5,2] [4,1] [3,0],实现时不用循环按组处理,我们可以从下标为step的元素开始,逐个跨组处理。同时,在插入数据时,可以采用元素交换法寻找最终位置,也可以采用数组元素移动法寻觅。希尔排序的代码比较简单,共包含三层for循环,如下:

public static int[] shell_sort(int[] nums) {
    int[] arr = Arrays.copyOf(nums, nums.length);
    // 迭代逐步缩小增量step
    for (int step=nums.length/2; step>0; step/=2) {
        // 从下标为step的元素开始,逐个对其所在组进行直接插入排序操作
        for (int i=step; i<nums.length; i++) {
            int target = arr[i];
            int j;
            // 移动插入法
            for (j=i; j-step>=0 && target<arr[j-step]; j-=step) {
                arr[j] = arr[j-step];
            }
            arr[j] = target;
        }
    }
    return arr;
}

增量序列及时间复杂度

Shell排序的执行时间依赖于增量序列。

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

① 最后一个增量必须为1;

② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。

img

image

Shell 增量序列

Shell 增量序列的递推公式为:
$$
h_t=N/2,h_k=h_{k+1}/2
$$
对于每次除以 2 的增量选择,希尔排序的最好情况当然是本身有序,每次分区都不用排序,时间复杂度是 O(n);但是在最坏的情况下仍然每次都需要移动,时间复杂度与直接插入排序在最坏情况下的时间复杂度没什么区别,也是 O(n^2)

Shell 增量序列构造代码如下:

int IncrementSequence_Shell[35];
void IncrementSequenceBuild_Shell()
{
    IncrementSequence_Shell[0]=1;
    IncrementSequence_Shell[1]=n;
    while(IncrementSequence_Shell[IncrementSequence_Shell[0]]>1)
        IncrementSequence_Shell[++IncrementSequence_Shell[0]]=IncrementSequence_Shell[IncrementSequence_Shell[0]-1]/2;
}

Shell增量序列不满足互质的要求,因此在上方图1所示的例子里,前几个增量没有起到任何作用(只起到了拖延时间的作用哈哈)。

于是有些大佬们就整出了下面这些增量序列:Hibbard增量序列Knuth增量序列Sedgewick增量序列等等。

Hibbard增量序列

Hibbard增量序列的通项公式为:
$$
h_i=2^i−1
$$
Hibbard增量序列的递推公式为:
$$
h_1=1,h_i=2∗h_{i-1}+1
$$
即:
$$
{1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191...}
$$

最坏时间复杂度为

标签:arr,排序,step,希尔,Shell,增量,序列,数据结构
来源: https://www.cnblogs.com/cciejh/p/14161072.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有