• 排序算法系列:Shell 排序算法


    概述

    希尔排序(Shell Sort)是 D.L.Shell 于 1959 年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是 O(n2) 的,希尔排序算法是突破这个时间复杂度的第一批算法之一。希尔排序是一种插入排序算法。


    版权说明

    著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    本文作者:Coding-Naga
    发表日期: 2016年4月12日
    本文链接:http://blog.csdn.net/lemon_tree12138/article/details/51127533
    来源:CSDN
    更多内容:分类 >> 算法与数学


    目录


    算法简介

    希尔排序(Shell Sort)是 D.L.Shell 于 1959 年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是 O(n2) 的,希尔排序算法是突破这个时间复杂度的第一批算法之一。
                                                  – 《大话数据结构》


    算法原理分析

    在上一篇《排序算法系列:插入排序算法》一文中,我们说到了一种复杂度为 O(n2) 的排序算法。而对于插入排序而言,如果我们的排序序列基本有序,或是数据量比较小,那么它的排序性能还是不错的。为什么?可能你会这样问。数据量小,这个不用多说,对于多数排序算法这一点都适用;如果一个序列已经基本有序(序列中小数普遍位于大数的前面),那么我们在插入排序的时候,就可以在较少的比较操作之后使整体有序。如果,这一点你还不是很明白,可以先看看《排序算法系列:插入排序算法》一文中的解释。
    基于上面基本有序和小数据量的提示,这里就有了希尔排序的实现。希尔所做的就是分组,在每个分组中进行插入排序。如下就是希尔排序中分组的示意图:
    这里写图片描述
    在上图中,我们将一个长度为 10 的序列,分组成 3 组。除最后一组以外的分组都包含了 4 个元素。可是,我们排序的时候并不是在这些分组内部进行的。而是我们要按照上面圆形的标号来进行插入排序,也就是排序中的 [4, 9, 7]。你可以先想一想这是为什么,在文章的最后,我再说明这个问题。

    上面从详细地说明了希尔排序的由来及希尔排序的分组精髓。那么现在就要说希尔排序中的插入排序,是的没有错。毕竟希尔排序的核心就是分组+插入排序嘛。在这个示意图中,我们可以很明显地看出它想表达的东西。这里只是将[3, 9, 7]看成了一个整体,对于其它的元素,暂时与[3, 9, 7]无关。
    这里写图片描述

    通过上面的两步操作,我们可以得到一个序列 T1 = [4, 0, 6, 1, 7, 2, 8, 5, 9, 3]。咦,这个序列还是无序的呀,怎么回事?我们说希尔排序的核心是分组和获得一个基本有序的数组。这里说的是基本有序,并未做出承诺说这个序列已经有序。那么,现在要做的就是继续分组+插入排序。当然,对应的 step 是要进行修改的。详细过程,参见下面的算法步骤。


    算法步骤

    通过上面的算法原理分析,这里可以总结出算法实现的步骤。如下:
    1. 获得一个无序的序列 T0 = [4, 3, 6, 5, 9, 0, 8, 1, 7, 2],并计算出当前序列状态下的步长 step = length / 3 + 1;
    2. 对序列 T0 按照步长周期分组;
    3. 对每个子序列进行插入排序操作;
    4. 判断此时的步长 step 是否大于 1?如果比 1 小或是等于 1,停止分组和对子序列的插入排序,否则,就继续;
    5. 修改此时的步长 step = step / 3 + 1,并继续第 2 到第 4 步;
    6. 排序算法结束,序列已是一个整体有序的序列。


    逻辑实现

    这是Shell 排序的核心模块,也是分组的关键步骤

    private void core(int[] array) {
            int arrayLength = array.length;
            int step = arrayLength;
            do {
                step = step / 3 + 1;
                for (int i = 0; i < step; i++) {
                    insert(array, i, step);
                }
                System.err.println(Arrays.toString(array));
            } while (step > 1);
        }

    希尔排序的直接插入排序,注意这里不同的是它只是针对某一个分组子序列进行插入排序

    private void insert(int[] array, int offset, int step) {
            int arrayLength = array.length;
            int groupCount = arrayLength / step + (arrayLength % step > offset ? 1 : 0);
            for (int i = 1; i < groupCount; i++) {
                int nextIndex = offset + step * i;
                int waitInsert = array[nextIndex];
    
                while(nextIndex - step >= 0 && waitInsert < array[nextIndex - step]) {
                    array[nextIndex] = array[nextIndex - step];
                    nextIndex -= step;
                }
    
                array[nextIndex] = waitInsert;
            }
        }

    更多详细逻辑实现,请参见文章结尾的源码链接。


    复杂度分析

    排序方法 时间复杂度 空间复杂度 稳定性 复杂性
    平均情况 最坏情况 最好情况
    Shell 排序 O(n3/2) O(n2) O(n) O(n) 不稳定 较复杂

    问题解答

    1. 为什么我们排序的时候并不是在这些分组内部进行的?
      答:这里我们分组的目的在于要完成的是对整个序列的基本序列处理,那么我们肯定想要让这些分组的数据尽量地分散开。如果要针对每个分组内部进行插入排序,那么之后的每次操作,都会是在内部进行的,这样的结果就是每个分组内部已经有序,只是整体仍然是无序的。
    2. 分组步长的计算公式为什么是 step = step / 3 + 1?
      答:这里的步长计算很关键,可是究竟应该选取什么样的增量才是最好的,目前还尚未解决。不过大量的研究表明,当增量序列为 dlta[k] = 2tk+1 - 1 (0 <= k <= t <= [log2(n+1)])时,可以获得不错的效果,其时间复杂度为 O(n3/2)。

    Ref

    • 《大话数据结构》

    Github源码下载

    https://github.com/William-Hai/ArraySortAlgorithm/blob/master/src/org/algorithm/array/sort/impl/ShellSort.java

  • 相关阅读:
    多测师讲解jmeter _ 导入本地文本内容参数化方法一__(1)高级讲师肖sir
    深圳_多测师面试 _平安项目总结(2020年10月)_高级讲师肖sir
    多测师讲解jmeter _token提取_高级讲师肖sir
    多测师讲解jmeter _图片详解_(全)高级讲师肖sir
    深圳精英面试总结——华为外包面试,明源云客,有咖面试,招商面试 总结(001)
    多测师讲解接口测试 —jmeter接数据库(004)_高级讲师肖sir
    多测师讲解jmeter _接口请求_(003)高级讲师肖sir
    多测师讲解jmeter _安装和配置环境(00)_高级讲师肖sir
    多测师讲解接口 _需求文档(用户增删改查)_高级讲师肖sir
    jemeter参数化读取文件
  • 原文地址:https://www.cnblogs.com/fengju/p/6335997.html
Copyright © 2020-2023  润新知