• 排序算法之希尔排序(缩小增量排序)


            前面两篇介绍了两个非常简单又非常基础的算法——选择排序和插入排序,并通过一篇关于大乐透的小应用程序介绍了插入排序的一个简单应用。本篇介绍一个基于插入排序算法的、快速的排序算法——希尔排序。同样,本篇主要从“基本原理、排序流程、核心代码、算法性能、稳定性、参考代码”等几个方面介绍这一算法。

            对于大规模的乱序的序列,插入排序算法执行效率并不太高,因为它只会交换相邻的元素,元素只能一点一点地从序列的一端移动到另一端,尤其是对于逆序的序列排序,每一趟小的元素只能一步一步走到前面。希尔排序正是在此基础上改进了插入排序,它可以交换不相邻的元素,来对序列进行局部排序,并最终将局部有序的序列完全排序。有关插入排序算法请见 http://www.cnblogs.com/Y-oung/p/7745197.html

            基本原理:将长度为N的序列中的元素每间隔h(h称为增量或步长)分成一组,同时用插入排序算法对每组进行排序。然后缩小增量h,重新分组排序,直到h=1时,对所有元素进行一次插入排序,此时序列已经有序。因为每次都需要缩小增量h的值,因此希尔排序又称为缩小增量排序。

            排序流程:以下以序列:9 0 8 1 7 3 2 6 4为例,表格1加粗元素表示增量h下的同一组元素,未加粗元素表示未参与比较也未移动的元素,设初始增量h=N/2=5,此后增量h=h/2。

    表格1  根据增量h分成不同的组,按组进行插入排序
    组数 排序前 排序后 增量h 说明
    1 5 9 0 8 1 7 3 2 6 4
    5 9 0 8 1 7 3 2 6 4
    5 5和7为一组,排序后顺序不变
    2 9 0 8 1 7 3 2 6 4
    3 0 8 1 7 9 2 6 4 5 9和3为一组,排序后交换位置
    3 5 3 0 8 1 7 9 2 6 4
    5 3 0 8 1 7 9 2 6 4 5 0和2为一组,排序后顺序不变
    4 5 3 0 8 1 7 9 2 6 4 5 3 0 6 1 7 9 2 8 4 5 8和6为一组,排序后交换位置
    5 5 3 0 6 1 7 9 2 8 4 5 3 0 6 1 7 9 2 8 4
    5 1和4为一组,排序后顺序不变
    6 5 3 0 6 1 7 9 2 8 4 0 3 1 6 5 7 8 2 9 4
    2 5 0 1 9 8为一组,排序后0 1 5 8 9
    7 3 1 6 5 7 8 2 9 4
    2 1 3 5 4 8 6 9 7 2 3 6 7 2 4为一组,排序后2 3 4 6 7
    8 0 2 1 3 5 4 8 6 9 7 0 1 2 3 4 5 6 7 8 9
    1 直接进行插入排序

            表格2加粗元素表示参与移动的元素,未加粗元素表示未移动的元素,设初始增量h=N/2=5,此后增量h=h/2。

    表2  根据增量h的不同,每趟插入排序详细过程
    趟数 序列 增量h
    1 5 9 0 8 1 7 3 2 6 4 5
    2 3 0 8 1 7 9 2 6 4 5
    3 5 3 0 8 1 7 9 2 6 4 5
    4 5 3 0 6 1 7 9 2 8 4 5
    5 5 3 0 6 1 7 9 2 8 4 5
    6 0 3 5 6 1 7 9 2 8 4 2
    7 0 3 5 6 1 7 9 2 8 4 2
    8 0 3 1 6 5 7 9 2 8 4 2
    9 0 3 1 6 5 7 9 2 8 4 2
    10 0 3 1 6 5 7 9 2 8 4 2
    11 2 1 3 5 6 9 7 8 4 2
    12 0 2 1 3 5 6 8 7 9 4 2
    13 0 2 1 3 5 4 8 6 9 7 2
    14 0 2 1 3 5 4 8 6 9 7 1
    15 0 1 2 3 5 4 8 6 9 7 1
    16 0 1 2 3 5 4 8 6 9 7 1
    17 0 1 2 3 5 4 8 6 9 7 1
    18 0 1 2 3 4 5 8 6 9 7 1
    19 0 1 2 3 4 5 8 6 9 7 1
    20 0 1 2 3 4 5 6 8 9 7 1
    21 0 1 2 3 4 5 6 8 9 7 1
    22 0 1 2 3 4 5 6 7 8 9 1

            最终排序结果为:0 1 2 3 4 5 6 7 8 9。

            核心代码:以Java为例。

    public static void sort(int[] a) {
        int n = a.length;  //序列长度
        int h = n/2;  //初始增量h为序列长度的一半
        while (h >= 1) {
            for (int i = h; i < n; i++) {
                for (int j = i; j >= h && a[j]<a[j-h]; j -= h) {
                    int swap = a[j];
                    a[j] = a[j-h];
                    a[j-h] = swap;
                }
            }
            h /= 2;  //增量减半
        }
    }

            算法性能:对任意随机序列排序,目前在数学上还不知道希尔排序所需要的平均比较次数,准确说明希尔排序的性能至今仍是一项挑战。事实上,希尔排序是目前唯一无法准确描述其对于乱序序列的性能特征的排序算法。尽管如此,可以肯定的是它的运行时间达不到平方级别。希尔排序的时间复杂度和增量h的选取有关,例如当增量h=1时,相当于进行了一次插入排序,时间复杂度为O(N²);而Hibbard增量的希尔排序时间复杂度为O(N3/2),所以无法准确说出希尔排序的时间复杂度,而希尔排序的空间复杂度为O(1)。可以看到,增量对希尔排序至关重要,遗憾的是,虽然有很多论文专门研究过不同的增量对希尔排序的影响,但都无法证明某个增量是最好的。下面一篇我会选取不同增量,专门测试一下不同增量对希尔排序效率的影响。

            稳定性:希尔排序是不稳定的排序方法。例如序列[2,1,1,3],取增量为2,第一趟排序第一个元素2会和第三个元素1交换位置([1,1,2,3]),最终两个1元素的相对位置发生改变,所以是不稳定的。正因为希尔排序会根据增量分组的特性,导致相同大小的元素可能会被分到不同的组中,所以相对位置可能会发生改变,导致希尔排序是不稳定的。

            综上所述,希尔排序的过程中增量会由大变小最后变成1,增量大的时候先把序列中相距较远的元素排序,这样基本上小的元素在前,大的元素在后,宏观来看序列基本有序;然后缩小增量,当增量很小或为1 的时候,序列就基本有序了,只需对序列微调即可,不会移动大量元素。因为插入排序对基本有序的序列排序效率较高,所以希尔排序其实就是逐渐使序列基本有序的过程,所以说希尔排序是对插入排序的改进。

            参考代码:以Java为例。

    import java.util.Random;
    
    /*
     * 希尔排序
     */
    
    public class ShellSort {
    
        public static void sort(int[] a) {
            int n = a.length;  //序列长度
            int h = n/2;  //初始增量h为序列长度的一半
            while (h >= 1) {
                for (int i = h; i < n; i++) {
                    for (int j = i; j >= h && a[j]<a[j-h]; j -= h) {
                        int swap = a[j];
                        a[j] = a[j-h];
                        a[j-h] = swap;
                    }
                }
                h /= 2;  //增量减半
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Random random = new Random();
            int[] arg1 = new int[20];
            for(int n=0;n<20;n++){  //从[0-100]中生成100个随机数
                arg1[n] = random.nextInt(100);
            }
            System.out.println("排序前:");
            for (int i = 0; i < arg1.length; i++) {
                System.out.print(arg1[i]+" ");
            }
            System.out.println("
    排序后:");
            long startTime = System.currentTimeMillis();  //获取开始时间
            sort(arg1);
            long endTime = System.currentTimeMillis();  //获取结束时间
            for (int i = 0; i < arg1.length; i++) {
                System.out.print(arg1[i]+" ");
            }
            System.out.println("
    排序时长:"+(endTime-startTime)+"ms");//16
        }
    
    }

            执行结果:

    排序前:
    41 64 98 77 21 7 87 87 74 3 96 18 67 54 64 85 22 61 83 46 
    排序后:
    3 7 18 21 22 41 46 54 61 64 64 67 74 77 83 85 87 87 96 98 
    排序时长:0ms

            转载请注明出处 http://www.cnblogs.com/Y-oung/p/7768908.html

            工作、学习、交流或有任何疑问,请联系邮箱:yy1340128046@163.com

  • 相关阅读:
    第01组 Alpha冲刺 总结
    第01组 Alpha冲刺 (6/6)
    小黄衫买家秀及获奖感言
    软工实践个人总结
    实验 7:OpenDaylight 实验——Python 中的 REST API 调用
    实验 6:OpenDaylight 实验——OpenDaylight 及 Postman 实现流表下发
    实验 5:OpenFlow 协议分析和 OpenDaylight 安装
    SDN实验 4:Open vSwitch 实验——Mininet 中使用 OVS 命令
    实验 3:Mininet 实验——测量路径的损耗率
    2020软件工程个人编程作业一:统计分析GitHub 的用户行为数据
  • 原文地址:https://www.cnblogs.com/Y-oung/p/7768908.html
Copyright © 2020-2023  润新知